现代 CSS

React中的props

特别声明:如果您喜欢小站的内容,可以点击申请会员进行全站阅读。如果您对付费阅读有任何建议或想法,欢迎发送邮件至: airenliao@gmail.com!或添加QQ:874472854(^_^)

组件是React中重要特性之一。我们可以将一个复杂的UI分解成多个基本组件。在开发了基本组件之后,我们可以将所需的组件组合在一起创建一个复杂的UI(也称为复杂的组件)。React中使用propsstate控制组件中的数据流。但刚接触React的同学对这些所谓的propsstate都会感到困惑,那么要掌握React就有必要掌握propsstate。在接下来的内容中我们主要围绕着React的propsstate和大家一起讨论。

组件

时至今日,在Web开发中都在提Web组件。在React中也避免不了这个话题,换句话说,组件的概念也是React中的三大支柱之一。使用React开发应用基本都是在使用组件(其实在Vue的开发也是类似,也是围绕着组件进行)。虽然我们在《React中创建组件的方式》和《初探React中函数组件和类组件的差异》和大家一起学习过React中创建组件的方式和不同方式之间的差异。但这些并不重要,重要的是我们想了解或者学习propsstate对组件的影响。

简单地说,在React中怎么通过propsstate来改变组件

那么什么是组件呢?@Linton Ye在他的博文《Components, Props and State》中用一个房子形象的描述了组件是什么?

整幢房子好比一个UI组件,只不过这个组件的每个部分都可以拆分出来成为一个独立的组件。正如上图所示,房子House是一个大组件,其中房顶Roof、窗户Window、门Door和墙Wall也是一个组件,并且这些组件组合在一起成为一个大的组件,即House组件。

如果我们换到一个Web页面中来说的话,上面的房子就好比下图:

也就是说,React组件被视为用户界面构建的模块。这些组件都存在于相同的空间中,但彼此又是独立执行的。React中的组件都有自己的结构方法。它们最大的特性就是可以重复使用。为了更好的理解,可以将整个UI看到一棵树,这棵树常常被称为UI树。比如上面的Web页面,如果转换成一棵UI树的话,大致像下面这样:

在这里,起始组件成为根(好比House,也类似HTML中的html),每个独立的部分成为分支(好比RoofWindowDoorWall,也类似上图中的1~5)。当然,这些分支中也可以进一步包含一些其他的分支。

而这些组件(UI)具有组织性,并且可以在根上根据状态和数据做出相应的更改,然后再流向子分支(子组件)。也可以简单地说,组件可以直接从客户端调用服务器,允许DOM在不刷新页面的情况下动态更新。也就是说,可以根据组件的propsstate对组件进行动态更新(简单地说,UI看上去不一样)

同样拿前面的House组件来说,其中Roof我们可以指定它是什么样的颜色(根据自己的喜好将房顶刷成自己喜欢的颜色),当然WallWindow以及Door也类似于Roof,但不同的是,门Door和窗户Window除了可以根据自己喜欢定制之外,它们还有另外的状态,比如说门是关闭的还是打开的(窗户也是相似的)。

如果将这些放到React中来描述的话,可以用propsstate来描述:

  • 不管是RoofWindowWallWindow还是Door都可以根据自己的喜欢定制自己的样式风格,而这些风格对应的就是React组件中的propsProperty的缩写)。如果换到HTML中来的话,props就相当于HTML标签元素的属性(即attributes
  • 对于WindowDoor而言除了可以定制自己喜欢的风格之外还有其他的状态,比如说他们是关闭状态还是打开状态。 相应的,在React中,我们可以通过state来控制

要是用一句话来描述的话:

props是不可变的,state是可变的;props是自己决定的,state是可以由外部来决定是否改变的。

看上去是一句简单的话,但对于React的初学者而言,propsstate是复杂的。既然他们是复杂的,我们就有必要一步一步的来分析和学习他们,只有这样我们才能更好的掌握好React。

propsstate是什么

在React组件中,state是影响组件渲染的内部数据集。在一定程度上,state可以看作是React组件的私有数据或数据模型。React组件state可变的。当React组件的内部state改变后,组件将根据新的状态重新渲染自己。比如WindowDoor可以是关闭的也可以是打开的。

props是React组件的属性,看起来像HTML属性(Attributes)。在React组件中的props的值通常从父组件中传递。

props的解释

propsproperties的简写,可以被定义为一种数据从组件传递到组件的方式,基本上就是从父组件传递到子组件。将一个数据从一个React组件传递到另一个组件,主要是因为不想让组件渲染静态数据,而是将动态数据传递给组件。这也正是React的props发挥其作用的地方。

同样拿@Linton Ye创建的房子来说,你可能创建多幢房子,但又不希望这些房子是千篇一律的。你可能喜欢红色的房顶,他可能喜欢蓝色的房顶,还有人喜欢粉红色的房顶。那么我们就可以通过给House组件透传一个props的值来控制房顶Roof的颜色。比如下面这样的一个示例:

详细代码请参考上面的示例,下面列出关键性代码。根据不同用户的喜好,创建的房子有可能房顶颜色会不一样。也就是说颜色color是一个动态的。在这里我们可以通过给House组件透传一个props属性值让用户可以根据自己喜欢配置房顶颜色:

const Roof = ({color}) => {
    return <img src={IMAGES.roof} className="roof" style={{background: `${color}`}} />
}

const House = (props) => {
    return (
        <div className='house'>      
            <Roof color={props.color} />
            <Wall />
            <Window />
            <Door />
        </div>
    )
}

const App = () => {
    return (
        <>
            <House color="blue" />
            <House color="red" />
            <House color="salmon" />
        </>  
    )
}

你可能已经看到了,blueredsalmon值传给了color属性(简称props),即这些值分别传递给<House />组件。

甚至我们可以更激进一些。比如我们希望房子除了房顶不一样之外,还可以其他部件不一样。简单地说,<House />组件的其他部位也可以像房顶一样通过props来透传。就上面的示例,我们可以像下面这样来进行改造:

// 创建房顶组件
const Roof = ({color, roofSrc}) => {
    return <img src={roofSrc} className="roof" style={{background: `${color}`}} />
}

// 创建墙
const Wall = ({wallSrc}) => {
    return <img src={wallSrc} className='wall' />;
}

// 创建窗户
const Window = ({windowSrc}) => {
    return <img src={windowSrc} className='window' />;
}

// 创建门
const Door = ({doorSrc}) => {
    return <img src={ doorSrc } className='door' />;
}

// 创建房子
const House = (props) => {
    return (
        <div className='house'>      
            <Roof color={props.color} roofSrc={props.roofSrc} />
            <Wall wallSrc={props.wallSrc} />
            <Window windowSrc={props.windowSrc} />
            <Door doorSrc={props.doorSrc} />
        </div>
    )
}

const App = () => {
    return (
        <House 
            color="blue"
            roofSrc="https://cdn.glitch.com/1fb3273a-81cb-45bc-acbd-555546cb098f%2Froof.png?1515785259159"
            wallSrc="https://cdn.glitch.com/1fb3273a-81cb-45bc-acbd-555546cb098f%2Fwall.png?1501113882297"
            windowSrc="https://cdn.glitch.com/1fb3273a-81cb-45bc-acbd-555546cb098f%2Fwindow.png?1501113882112"
            doorSrc="https://cdn.glitch.com/1fb3273a-81cb-45bc-acbd-555546cb098f%2Fdoor_close.png?1501113881433"
        />
    )
}

你将看到的效果如下:

另外,在React中不能直接将属性传递给目标组件。这是因为React遵循这样的规则:

属性必须从父组件流向直接的子组件

这意味着在发送属性时不能跳过子组件层,子组件也不能将属性发送回父组件。在实际使用的时候,还可以使用默认的props值,以防父组件没有向下传递props。比如上面的示例,我们就可以给每个子组件设置一个默认的props值:

const House = (props) => {
    // 声明房子默认需要的属性值
    const IMAGES = {
        roof: 'https://cdn.glitch.com/1fb3273a-81cb-45bc-acbd-555546cb098f%2Froof.png?1515785259159',
        wall: 'https://cdn.glitch.com/1fb3273a-81cb-45bc-acbd-555546cb098f%2Fwall.png?1501113882297',
        window: 'https://cdn.glitch.com/1fb3273a-81cb-45bc-acbd-555546cb098f%2Fwindow.png?1501113882112',
        door: 'https://cdn.glitch.com/1fb3273a-81cb-45bc-acbd-555546cb098f%2Fdoor_close.png?1501113881433'
    }
    return (
        <div className='house'>      
            <Roof color={props.color || 'blue'} roofSrc={props.roofSrc || IMAGES.roof} />
            <Wall wallSrc={props.wallSrc || IMAGES.wall} />
            <Window windowSrc={props.windowSrc || IMAGES.window} />
            <Door doorSrc={props.doorSrc || IMAGES.door} />
        </div>
    )
}

const App = () => {
    return (
        <>
            {/* 默认房子*/}
            <House />
            {/* 定制房子(门是打开的)*/}
            <House 
                color="salmon"
                doorSrc="https://cdn.glitch.com/1fb3273a-81cb-45bc-acbd-555546cb098f%2Fdoor_open.png?1501113882121"
                />
        </>
    )
}

渲染出来的效果如下所示,左边是默认房子的效果,右边是定制房子的效果(门是打开的):

通过上面简单地实例,我们可以得知,React中数据绑定是单向的

因此,在这种情况下,我们需要一层一层地发送数据,直到它到达目标子组件。这个路径中的每个组件都必须从其父组件接收该该属性,然后将该属性作为接收重新发到其子组件。这个过程会不断重复,直到属性到达它的目标组件。如下图所示:

上面我们看到的是在函数组件中怎么从父组件House组件将需要的props透传给其子组件。由于在React中除了可以通过函数来构建组件之外还可以使用class来构建组件。如果使用的是class构建组件,又将怎样从父组件向子组件透传props呢?

在使用class方式创建的组件中,可以通过this.props方式将props透传给子组件。下面这个示例就是使用class方式改造的,具体代码如下:

// 创建房顶组件
const Roof = ({color, roof}) => {
    return <img src={roof} className="roof" style={{background: `${color}`}} />
}

// 创建墙
const Wall = ({wall}) => {
    return <img src={wall} className='wall' />;
}

// 创建窗户
const Window = ({window}) => {
    return <img src={window} className='window' />;
}

// 创建门
const Door = ({door}) => {
    return <img src={ door } className='door' />;
}

// 创建房子

class House extends React.Component{
    static defaultProps = {
        color: 'blue',
        roof: 'https://cdn.glitch.com/1fb3273a-81cb-45bc-acbd-555546cb098f%2Froof.png?1515785259159',
        wall: 'https://cdn.glitch.com/1fb3273a-81cb-45bc-acbd-555546cb098f%2Fwall.png?1501113882297',
        window: 'https://cdn.glitch.com/1fb3273a-81cb-45bc-acbd-555546cb098f%2Fwindow.png?1501113882112',
        door: 'https://cdn.glitch.com/1fb3273a-81cb-45bc-acbd-555546cb098f%2Fdoor_close.png?1501113881433'
    }

    constructor (props) {
        super(props)
    }

    render() {
        return (
            <div className='house'>      
                <Roof color={this.props.color} roof={this.props.roof } />
                <Wall wall={this.props.wall} />
                <Window window={this.props.window} />
                <Door door={this.props.door} />
            </div>
        )
    }
}

const App = () => {
    return (
        <>
            <House />
            <House 
                color="salmon"
                door="https://cdn.glitch.com/1fb3273a-81cb-45bc-acbd-555546cb098f%2Fdoor_open.png?1501113882121"
                />
        </>
    )
}

效果如下:

在该示例中,没有像在函数组件中通过||来设置props的默认值,而是使用了defaultProps来设置props的默认值。如果<House />组件中没有显式透传任何值时,也不需要去判断配置属性是否有传进来:如果没有传进来,会直接使用defaultProps中的默认值。所以可以看到,在render()函数中,我们直接使用了this.props而不需要再做判断;如果透传了值进来,就会显示透传的值,比如上例中右侧的House组件,就显式的透传了colordoor属性的值,所以看到的房子和左侧的不一样。

React中props的其他使用方式

前面我们通过一幢房子为例,和大家一起学习了React的props的基础知识,其实在React中使用props可谓简单,但也不易,也有很多方式和场景会给我们初学者带来不少的困惑。接下来,通过一些简单的实例和大家一起来聊聊。

如何从子组件向父组件透传props

前面提到过:

在React中无法将props从子组件传递给父组件

这对于初学者来说,是一个常见的问题。在React中如果要实现从子组件向父组件传值这样的功能会比较绕一点。比如下面这样的一个点赞组件。

这个组件分成两个部分,一个是心形(赞和未赞不同的两个效果),另一部分是文案“赞”和“取赞”。整个代码大致如下:

class LikeButton extends React.Component{
    constructor(props){
        super(props)
        this.state = {
            isLiked: false
        }
        this.handleToggleLiked = this.handleToggleLiked.bind(this)
    }

    handleToggleLiked = () => {
        this.setState({
            isLiked: !this.state.isLiked
        })
    }

    render() {
        return (
            <div onClick={this.handleToggleLiked} className={this.state.isLiked ? 'like' : 'unlike'}></div>
        )
    }
}

const ButtonText = ({isLiked}) => {
    return <div>{isLiked ? '赞' : '取赞'}</div>
}

class App extends React.Component{
    render() {
        const isLiked = false
        return (
            <div className="button">
                <LikeButton />
                <ButtonText isLiked={isLiked} />
            </div>
        )
    }
}

在该示例中,你点击点赞按钮(心形)时它自身能在填充(赞)和非填充(取赞)两个状态之间切换,但是对应的文案不能正常切换。到目前为止,按钮组件LikeButton管理了它自己的状态isLiked(在falsetrue之间切换)。由于是LikeButton组件管理isLiked属性,因此无法将其作为props传给App组件。就上面示例而言,ButtonText组件需要App组件的isLiked属性,用来控制该组件文案显示(“赞”和“取赞”)。现在是无法以种方式正常工作(因为子组件的props无法透传给父组件)。如果需要让程序正常运行的话就必须提升状态(state以使其他组件(在本例中是App组件)可以作为state(或作为传递给其他组件的props)访问的要点。

const LikeButton = ({onClick, isLiked}) => {
    return <div onClick={onClick} className={isLiked ? 'like' : 'unlike'}></div>
}

const ButtonText = ({isLiked}) => {
    return <div>{isLiked ? '赞' : '取赞'}</div>
}

class App extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            isLiked: false
        }
        this.handleToggleLike = this.handleToggleLike.bind(this)
    }

    handleToggleLike = () => {
        this.setState({
            isLiked: !this.state.isLiked
        })
    }

    render() {
        return (
            <div className="button">
                <LikeButton onClick={this.handleToggleLike} isLiked={this.state.isLiked} />
                <ButtonText isLiked={this.state.isLiked} />
            </div>  
        )
    }
}

此处的修改重点是isLiked属性从<LikeButton />中提取出来放在<App />组件中(isLiked上升到父组件)。现在<App />组件可以将一个函数作为props传递给<LikeButton />组件,而且该函数(比如handleToggleLike())用于<LikeButton />组件中的onClick事件中。但是,<LikeButton />组件并不知道handleToggleLike()函数中的业务逻辑,只知道在单击按钮时必须触发handleToggleLike()函数。在上面的<App />组件中,当调用传递的函数时,状态会发生变化(<LikeButton />点击会触发handleToggleLike()函数,该函数会让isLiked属性在falsetrue两个状态之间切换),因此所有受影响的组件(使用变化的state或将其作为props使用),比如该例中的<LikeButton /><ButtonText />都会因为isLiked的变化重新渲染。

如前所述,在React中不能将props从子组件中传道到父组件,但可以从父组件向子组件传递一个函数,而子组件使用这些函数,并且这些函数可能会更改上面父组件中的state。一但状态改变了,状态就会再次作为props传递下去给子组件。所有受影响的组件将会重新渲染。

该示例中我们涉及到了React的state,如果不清楚并不要紧,后面的内容我们会花一定的篇幅来和大家一起学习state相关的知识。

React中的...props语法

在React中还可以使用JavaScript的spread...)操作符将所有props传递给子组件。JavaScript的...操作符在React中是一个非常有用的特性,也有人将其称为React的...props语法,但事实上它并不是React真正的功能,仅仅是来自于JavaScript的一个特性。

回到我们前面所介绍的房子组件中,我们就可以通过...props来完成:

// 创建房顶组件
const Roof = ({color, roof}) => {
    return <img src={roof} className="roof" style={{background: `${color}`}} />
}

// 创建墙
const Wall = ({wall}) => {
    return <img src={wall} className='wall' />;
}

// 创建窗户
const Window = ({window}) => {
    return <img src={window} className='window' />;
}

// 创建门
const Door = ({door}) => {
    return <img src={ door } className='door' />;
}

// 创建房子
const House = ({color, roof, wall, window, door}) => {
    return (
        <div className='house'>      
            <Roof color={color} roof={roof} />
            <Wall wall={wall} />
            <Window window={window} />
            <Door door={door} />
        </div>
    )
}

const App = () => {
    const houses = {
        color: 'orange',
        roof: 'https://cdn.glitch.com/1fb3273a-81cb-45bc-acbd-555546cb098f%2Froof.png?1515785259159',
        wall: 'https://cdn.glitch.com/1fb3273a-81cb-45bc-acbd-555546cb098f%2Fwall.png?1501113882297',
        window: 'https://cdn.glitch.com/1fb3273a-81cb-45bc-acbd-555546cb098f%2Fwindow.png?1501113882112',
        door: 'https://cdn.glitch.com/1fb3273a-81cb-45bc-acbd-555546cb098f%2Fdoor_close.png?1501113881433'
    }
    return (
        <>
            <House {...houses} />
            <House 
                {...houses}
                color="salmon"
                door="https://cdn.glitch.com/1fb3273a-81cb-45bc-acbd-555546cb098f%2Fdoor_open.png?1501113882121"
            />
        </>
    )
}

渲染出来的结果如下:

在React中使用...props可以将整个对象的键值传给子组件。它的效果和将对象的每个值单独传给子组件一样。子组件也以与以前相同的方式获取props。另外在子组件中还常常使用...rest来建立...props。比如上面的示例,还可以改造成下面这样:

// 修改的地方
const House = ({roof, wall, window, door, ...rest}) => {
    return (
        <div className='house'>      
            <Roof roof={roof} {...rest} />
            <Wall wall={wall} />
            <Window window={window} />
            <Door door={door} />
        </div>
    )
}

你将看到的效果是一样的:

正如你所看到的,在House组件中除了显式声明了构建组件所需的props(比如roofwallwindowdoor,相比前面示例去掉了color)之外还声明了...rest(你也可以用其他的名称,比如...other)。...rest在这的意思就是一个拥有所有剩余属性的对象(在本例中就是用来设置房顶组件roof的颜色color)。

React中的children

作为React的新手,估计和我一样也在一些项目或教程中看到过有关于this.props.children身影。有很多时候对它的目的不明确,但children有其独特优势。

React的优势之一呢就是可以用很多个子组件拼装出一个复杂的组件。在一个容器组件中拼入不同的子组件可以组装成不同的组件,比如下图所示:

好比上图所示,绿色是一个容器组件,在该组件拼入不同的子组件时就可以得到不一样的效果。这样说可能大家还存有一定的困惑。为了更好的和大家能把React的children聊清楚,我们来看一个卡片组件(Card)。至于Card组件里面是什么?我们都有可能不清楚,也就是说,在该示例中Card仅充当了一个容器的作用,它定义了一种外层结构形式,然后你可以在里面填充任何你需要的内容。如果回到我们的HTML结构中来说,他可能是最简单的一个标签之一:

<div class="card">
    <!-- 这里可以填充任意你想要的内容 -->
</div>

也就是说,Card组件本身是一个不带任何内容的容器。在React中要实现这样的一个组件,就需要借助props.children(即children)。

在介绍children之前,先回到我们前面所学的props知识,比如下面这个组件:

class Card extends React.Component {
    render () {
        return (
            <div className="card">
                {this.props.content}
            </div>
        )
    }
}

const App = () => {
    const content = (
        <div className="card-body">
            <h5 className="card-title">Card title</h5>
            <p className="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
            <a href="#" className="btn btn-primary">Go somewhere</a>
        </div>
    )
    return <Card content={content} />
}

而事实上,我们在实际业务中并没有像上面示例来得那么简单,如查我们想要实现下面这样的一个卡片效果:

事情会变得更为复杂一点。如果继续按上面一个content这样的props来传值也不是件易事(JSX代码难于维护)。

咱们暂时忽略上面卡片的效果怎么实现。回到上面那个简单的Card实例中来。上面的示例将Card组件要渲染的内容都显式的声明在content变量中,然后传给Card组件的content这个props。也就是说,如果我们在调用Card组件时能否像调用一个HTML标签,事情是不是会变得更容易一些,比如像下面这样

const App = () => {
    return (
        <Card>
            <div className="card-body">
                <h5 className="card-title">Card title</h5>
                <p className="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
                <a href="#" className="btn btn-primary">Go somewhere</a>
            </div>
        </Card>
    )
}

看上去怎么特像Vue中的slot(插槽)呢?如果没有接触过Vue的slot,可以花点时间阅读:

在React中没有slot或者说v-slot这样的指令,但React有children。也就是说,在React中组件内部可以通过props.children来实现类似于Vue的slot的效果。简单地说,通过children可以像一个HTML标签一样,在Card组件中插入任意你想要的内容。比如上面的示例把this.props.content更换成this.props.children

class Card extends React.Component {
    render () {
        return (
            <div className="card">
                {this.props.content}
            </div>
        )
    }
}

App组件中再次调用Card组件时就可以像使用HTML标签一样:

const App = () => {
    return <Card>
        <div className="card-body">
            <h5 className="card-title">Card title</h5>
            <p className="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
            <a href="#" className="btn btn-primary">Go somewhere</a>
        </div>
    </Card>
}

最终效果是一样的:

React中的children是唯一没有通过属性传递的props,比如onClickonChangekeystyle。相反,当你使用React组件(被认为是父组件)包裹它时,它将被隐式传递。

另外,如果我们把this.props.children打印出来,你可以看到它其实是个数组:

这种嵌套的内容成为了props.children数组的机制使用我们编写组件变得非常的灵活。我们甚至可以在组件内部把数组中的JSX元素安置在不同的地方。换句话说,我们需要创建一个可重用的组件,但是props.children并不能满足这个需求。当组件需要接受多个children时,并将它们放置在布局中。

我们回到前面那个卡片组件中来,该组件包含了CardHeaderCardBodyCardFooter等三个子组件。

<Card>
    <CardHeader />
    <CardBody />
    <CardFooter />
</Card>

我们可以使用多个props来实现,将需要的内容透传给Card组件:

<Card 
    header={<CardHeader />}
    body={<CardBody />}
    footer={<CardFooter />}
/>

对于<Card>组件,我们需要headerbodyfooter三个props

const Card = (props) => {
    return (
        <div className="card">
            <div className="card-header">{props.header}</div>
            <div className="card-body">{props.body}</div>
            <div className="card-footer">{props.footer}</div>
        </div>
    )
}

你可以想象Card组件在内部会更加复杂,有很多嵌套的div或用于实现所需样式效果的类名,或者它还有一些被传递进来的特定组件。但不管Card组件有多复杂,需要做什么,我们只需要关注透传进来的三个props,即headerbodyfooter

就像上面的示例,我们还可以把children当作props来进行传递。那么我们的Card组件可以按下面这样的方式来进行改造:

// Card
const Card = ({children}) => {
    return (
        <div className="card">{children}</div>
    )
}

// CardHeader
const CardHeader = ({children}) => {
    return (
        <div className="card-header">{children}</div>
    )
}

// CardBody
const CardBody = ({children}) => {
    return (
        <div className="card-body">{children}</div>
    )
}

// CardFooter
const CardFooter = ({children}) => {
    return (
        <div className="card-footer">{children}</div>
    )
}

const App = () => {
    return (
        <Card>
            <CardHeader>
                <h3>超级抵钱</h3>
                <div className="crad__header--extension">更多</div>
            </CardHeader>  
            <CardBody>
                <Media 
                mediaObject="//gw.alicdn.com/i1/O1CN01P2PGqg2JGVCsoEzRl_!!0-tejia.jpg_360x10000Q50.jpg" 
                mediaHeading="整箱2斤坚果巧克力威化饼"
                mediaContent="该商品参与了公益宝贝计划,卖家承诺每笔成交将为新未来高中生助学计划捐赠0.02元。"
                />
            </CardBody>
            <CardFooter>
                <Button type="outline-primary" buttonText="取消" />
                <Button type="primary" buttonText="确认" />
            </CardFooter>  
        </Card>  
    )
}

最终看到的效果是一样的:

可能你也留意到了,在示例中有使用this.props.children的,也有使用props.children的,两者的区别是前者用于使用class创建的组件,后面用于函数创建的组件中。

如何将组件作为props传递

其实在上面的示例中我们已经看到了将组件作为props传递,比发中在上面示例的<Card>组件中,把CardHeaderCardBodyCardFooter组件分别作为Card组件的headerbodyfooter三个props传进去:

<Card 
    header={<CardHeader />}
    body={<CardBody />}
    footer={<CardFooter />}
/>

在这个小节中,我们来详细看看在React中如何将一个组件作为props传递给另一个组件。

只要是接触过React的同学或许都知道,React组件可以由很其他的一些组件拼装而成。在开始了解组件作为props传递给组件之前,先来复习一下如何将多个组件拼装成另一个组件。比如下面这个用户头像的组件:

const AvatarRound = ({user}) => {
    return (
        <div className="avatar">
            <img className="round" alt={user.name} src={user.avatarUrl} />
        </div>
    )
}

const Profile = ({user, children}) => {
    return (
        <div className="profile">
            {children}
            <div className="name">
                <p>{user.name}</p>
            </div>
        </div>
    )
}

const User = ({user}) => {
    return (
        <Profile user={user}>
            <AvatarRound user={user}>
        </Profile>
    )
}

你看到的效果如下:

但是,如果希望将多个组件传递给一个组件,并且将它们放置在不同的位置,特别是不使用上一节说到的children,而使用常规的props,我们可以像下面这样做:

const AvatarRound = ({user}) => {
    return (
        <div className="avatar">
            <img className="round" alt={user.name} src={user.avatarUrl} />
        </div>
    )
}

const Profile = ({user, avatar, biography}) => {
    return (
        <div className="profile">
            {avatar}
            <div className="name">
                {user.name}
                {biography}
            </div>
        </div>
    )
}

const Biography = ({user}) => {
    return <p className="fat">{user.biography}</p>
}

const User = ({user}) => {
    return (
        <Profile
            user={user}
            avatar={<AvatarRound user={user} />}
            biography={<Biography user={user} />}
        />
    )
}

通常,这种方法用于将多个组件作为带有props的内容的布局组件。假设在你的页面中,用户头像除了圆的,可能还会有方的,那么这种方式就非常便利:

React中的render props

React官网是这样描述的

render props是指一种在React组件之间使用一个值为函数的props共享代码的简单技术。

简单是对于大神来说的,事实上render props对于初学者来说可不是件简单的事情。如果要彻底的了解清楚该技术,需要很长的篇幅来介绍。不过在这里我们不会过多的阐述该技术,只是让大家对该技术有一个最初步的接触。

具有render props的组件接受一个函数,该函数返回一个React元素并调用它而不是实现自己的渲染逻辑。其实现原理就是根据名为renderprops,通过render来改变自身的渲染逻辑。render中接受一个函数,通过函数方式返回一个新组件,只要有函数就可以通过传递参数的方式动态传递变量。此render函数由另一个组件定义,接收组件共享通过render函数传递的内容。

它看上去像这样:

class BaseComponent extends Component {
    render() {
        return <Fragment>{this.props.render()}</Fragment>;
    }
}

想象一下,假设你有一个礼盒,如果这个礼合自身就是一个<App />组件,该组件置于最顶部。如果这个盒子是我们正在创建的组件,并且我们打开了这个礼盒,那么一旦被render()调用,我们将公开使用组件所需的propsstate、函数和方法。

组件的render函数通常包含所有的JSX,这样就形成了该组件的DOM。相反,这个组件有一个render函数,即this.props.render(),它将显示一个通过props传递进来的组件。

我们来看一个示例,比如一个计数器组件<Counter />。创建一个包含了初始状态、方法和渲染的组件:

class Counter extends React.Component {
    state = {
        count: 0
    }

    increment = () => {
        const {count} = this.state
        return this.setState({count: count + 1})
    }

    decrement = () => {
        const {count} = this.state
        return this.setState({count: count - 1})
    }

    render() {
        const {count} = this.state

        return (
            <div>
                {
                    this.props.render({
                        increment: this.increment,
                        decrement: this.decrement,
                        count: count
                    })
                }
            </div>
        )
    }
}

Counter组件中,我们指定方法,而且是组件公开的内容。就该示例而言,我们需要一个递增(increment)和一个递减(decrement)方法。同时将默认的计数count最初值设置为0,方法中的逻辑也很简单,就是对count从一个0值开始做递增(或递减)。

正如你所看到的,在return()方法中我们使用了this.props.render()。正是通过这个函数传递来自Counter组件的方法和状态,以便由它包装的组件能够使用它。如果我们需要在<App />组件使用的话,它可以像下面这样使用:

class App extends React.Component {
    render() {
        return (
            <Counter
                render={({increment, decrement, count}) => (
                    <>
                        <h3>Render Props Counter</h3>
                        <div className="control">
                            <button onClick={()=> increment()}><span>+</span></button>
                            <span>{count}</span>
                            <button onClick={()=> decrement()}><span>-</span></button>
                        </div>
                    </>
                )}
            />
        )
    }
}

如果你对render props感兴趣的话,可以花些时间阅读下面这些教程:

小结

前面花了一些篇幅和大家一起学习和探讨了React中的propsstate中的props。简单的小结一下:

  • 为了让组件可定制性更高,我们可以创建组件时使用props在组件上添加属性来传入相关的配置参数
  • 组件可以在内部通过this.props(函数组件中使用props)获取到配置参数,组件可以根据props的不同来确定自己的渲染,达到可配置的效果
  • 可以通过给组件添加类属性defaultProps来配置默认参数
  • props一旦传入,就不可以在组件内部对它进行修改,但可以通过父组件主动重新渲染的方式来传入新的props,从而达到更新的效果
  • React中props必须从父组件流向直接的子组件;子组件无法直接将props传递给父组件

在后续将和大家继续一起学习和探讨React中的state相关的知识点,感兴趣的话,欢迎持续关注后续的相关更新。如果你在这方面有更好的建议和经验,欢迎在下面的评论中与我们一起分享。

返回顶部