React中的props
组件是React中重要特性之一。我们可以将一个复杂的UI分解成多个基本组件。在开发了基本组件之后,我们可以将所需的组件组合在一起创建一个复杂的UI(也称为复杂的组件)。React中使用props
和state
控制组件中的数据流。但刚接触React的同学对这些所谓的props
和state
都会感到困惑,那么要掌握React就有必要掌握props
和state
。在接下来的内容中我们主要围绕着React的props
和state
和大家一起讨论。
组件
时至今日,在Web开发中都在提Web组件。在React中也避免不了这个话题,换句话说,组件的概念也是React中的三大支柱之一。使用React开发应用基本都是在使用组件(其实在Vue的开发也是类似,也是围绕着组件进行)。虽然我们在《React中创建组件的方式》和《初探React中函数组件和类组件的差异》和大家一起学习过React中创建组件的方式和不同方式之间的差异。但这些并不重要,重要的是我们想了解或者学习props
和state
对组件的影响。
简单地说,在React中怎么通过props
和state
来改变组件。
那么什么是组件呢?@Linton Ye在他的博文《Components, Props and State》中用一个房子形象的描述了组件是什么?
整幢房子好比一个UI组件,只不过这个组件的每个部分都可以拆分出来成为一个独立的组件。正如上图所示,房子House
是一个大组件,其中房顶Roof
、窗户Window
、门Door
和墙Wall
也是一个组件,并且这些组件组合在一起成为一个大的组件,即House
组件。
如果我们换到一个Web页面中来说的话,上面的房子就好比下图:
也就是说,React组件被视为用户界面构建的模块。这些组件都存在于相同的空间中,但彼此又是独立执行的。React中的组件都有自己的结构和方法。它们最大的特性就是可以重复使用。为了更好的理解,可以将整个UI看到一棵树,这棵树常常被称为UI树。比如上面的Web页面,如果转换成一棵UI树的话,大致像下面这样:
在这里,起始组件成为根(好比House
,也类似HTML中的html
),每个独立的部分成为分支(好比Roof
、Window
、Door
和Wall
,也类似上图中的1~5
)。当然,这些分支中也可以进一步包含一些其他的分支。
而这些组件(UI)具有组织性,并且可以在根上根据状态和数据做出相应的更改,然后再流向子分支(子组件)。也可以简单地说,组件可以直接从客户端调用服务器,允许DOM在不刷新页面的情况下动态更新。也就是说,可以根据组件的props
或state
对组件进行动态更新(简单地说,UI看上去不一样)。
同样拿前面的House
组件来说,其中Roof
我们可以指定它是什么样的颜色(根据自己的喜好将房顶刷成自己喜欢的颜色),当然Wall
和Window
以及Door
也类似于Roof
,但不同的是,门Door
和窗户Window
除了可以根据自己喜欢定制之外,它们还有另外的状态,比如说门是关闭的还是打开的(窗户也是相似的)。
如果将这些放到React中来描述的话,可以用props
和state
来描述:
- 不管是
Roof
、Window
、Wall
、Window
还是Door
都可以根据自己的喜欢定制自己的样式风格,而这些风格对应的就是React组件中的props
(Property
的缩写)。如果换到HTML中来的话,props
就相当于HTML标签元素的属性(即attributes
) - 对于
Window
和Door
而言除了可以定制自己喜欢的风格之外还有其他的状态,比如说他们是关闭状态还是打开状态。 相应的,在React中,我们可以通过state
来控制
要是用一句话来描述的话:
props
是不可变的,state
是可变的;props
是自己决定的,state
是可以由外部来决定是否改变的。
看上去是一句简单的话,但对于React的初学者而言,props
和state
是复杂的。既然他们是复杂的,我们就有必要一步一步的来分析和学习他们,只有这样我们才能更好的掌握好React。
props
和state
是什么
在React组件中,state
是影响组件渲染的内部数据集。在一定程度上,state
可以看作是React组件的私有数据或数据模型。React组件state
是可变的。当React组件的内部state
改变后,组件将根据新的状态重新渲染自己。比如Window
和Door
可以是关闭的也可以是打开的。
而props
是React组件的属性,看起来像HTML属性(Attributes
)。在React组件中的props
的值通常从父组件中传递。
props
的解释
props
是properties
的简写,可以被定义为一种数据从组件传递到组件的方式,基本上就是从父组件传递到子组件。将一个数据从一个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" />
</>
)
}
你可能已经看到了,blue
、red
和salmon
值传给了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
组件,就显式的透传了color
和door
属性的值,所以看到的房子和左侧的不一样。
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
(在false
和true
之间切换)。由于是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
属性在false
和true
两个状态之间切换),因此所有受影响的组件(使用变化的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
(比如roof
、wall
、window
和door
,相比前面示例去掉了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
,比如onClick
、onChange
、key
和style
。相反,当你使用React组件(被认为是父组件)包裹它时,它将被隐式传递。
另外,如果我们把this.props.children
打印出来,你可以看到它其实是个数组:
这种嵌套的内容成为了props.children
数组的机制使用我们编写组件变得非常的灵活。我们甚至可以在组件内部把数组中的JSX元素安置在不同的地方。换句话说,我们需要创建一个可重用的组件,但是props.children
并不能满足这个需求。当组件需要接受多个children
时,并将它们放置在布局中。
我们回到前面那个卡片组件中来,该组件包含了CardHeader
、CardBody
、CardFooter
等三个子组件。
<Card>
<CardHeader />
<CardBody />
<CardFooter />
</Card>
我们可以使用多个props
来实现,将需要的内容透传给Card
组件:
<Card
header={<CardHeader />}
body={<CardBody />}
footer={<CardFooter />}
/>
对于<Card>
组件,我们需要header
、body
和footer
三个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
,即header
、body
和footer
。
就像上面的示例,我们还可以把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>
组件中,把CardHeader
、CardBody
和CardFooter
组件分别作为Card
组件的header
、body
和footer
三个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
render props
是指一种在React组件之间使用一个值为函数的props
共享代码的简单技术。
简单是对于大神来说的,事实上render props
对于初学者来说可不是件简单的事情。如果要彻底的了解清楚该技术,需要很长的篇幅来介绍。不过在这里我们不会过多的阐述该技术,只是让大家对该技术有一个最初步的接触。
具有render props
的组件接受一个函数,该函数返回一个React元素并调用它而不是实现自己的渲染逻辑。其实现原理就是根据名为render
的props
,通过render
来改变自身的渲染逻辑。render
中接受一个函数,通过函数方式返回一个新组件,只要有函数就可以通过传递参数的方式动态传递变量。此render
函数由另一个组件定义,接收组件共享通过render
函数传递的内容。
它看上去像这样:
class BaseComponent extends Component {
render() {
return <Fragment>{this.props.render()}</Fragment>;
}
}
想象一下,假设你有一个礼盒,如果这个礼合自身就是一个<App />
组件,该组件置于最顶部。如果这个盒子是我们正在创建的组件,并且我们打开了这个礼盒,那么一旦被render()
调用,我们将公开使用组件所需的props
、state
、函数和方法。
组件的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
感兴趣的话,可以花些时间阅读下面这些教程:
- Using Render Props In React
- React Render Props
- Using Functions as Children and Render Props in React Components
- Building React Components Using Children Props and Context API
- Render Props
- React Render Props
- From Hooks to... Render Props?
- React’s Render Props technique in 3 Minutes
- Understanding React Render Props by Example
- How to Solve Render Props Callback Hell
- Using custom hooks in place of "render props"
- Understanding React Render Props and HOC
小结
前面花了一些篇幅和大家一起学习和探讨了React中的props
和state
中的props
。简单的小结一下:
- 为了让组件可定制性更高,我们可以创建组件时使用
props
在组件上添加属性来传入相关的配置参数 - 组件可以在内部通过
this.props
(函数组件中使用props
)获取到配置参数,组件可以根据props
的不同来确定自己的渲染,达到可配置的效果 - 可以通过给组件添加类属性
defaultProps
来配置默认参数 props
一旦传入,就不可以在组件内部对它进行修改,但可以通过父组件主动重新渲染的方式来传入新的props
,从而达到更新的效果- React中
props
必须从父组件流向直接的子组件;子组件无法直接将props
传递给父组件
在后续将和大家继续一起学习和探讨React中的state
相关的知识点,感兴趣的话,欢迎持续关注后续的相关更新。如果你在这方面有更好的建议和经验,欢迎在下面的评论中与我们一起分享。