这篇主要写的是React中父子组件数据传递跟生命周期。
父子组件数据传递
上一篇其实有说到,组件跟组件之前其实是相对独立的。虽然每个组件的数据是可以独立处理,但是组件与组件之间的数据还是存在联系的,而所有的组件之间的数据关系,都是可以归结为父组件跟子组件之间的数据传递关系。再拆分这种数据传递关系,就是要解决父组件数据传给子组件,子组件数据传给父组件两个问题。
- 父组件传入参数。在React中,父组件给子组件传递数据其实挺简单的,就是在子组件标签中通过变量进行传入,子组件通过props获取传入的数据:
代码1 父组件传参1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41/* 自己写豆瓣的一段代码,
* 图书页面Book组件中把页面类型参数Contants.Book传入
* 搜索Search组件
* /
class Book extends Component {
render () {
return (
<Fragment>
<Search
page={this.state.book}
searchInfo={this.getSearchInfo}
/>
</Fragment>
)
}
}
/* 搜索Search组件通过props.page接收父组件的参数,
* 判断input标签的属性placeholder的值
* 项目中还使用了styled-components插件,后面会另有介绍
* /
class Search extends Component {
// 根据父组件传入的props.page确定input标签的属性placeholder的值
showPlaceHolder () {
const { placeHolders, page } = this.props
const placeHoldersJS = placeHolders.toJS()
return placeHoldersJS[page]
}
render () {
return (
<SearchWrapper>
<div>
<SearchInput
value={this.props.keyword}
placeholder={this.showPlaceHolder()}
/>
</div>
</SearchWrapper>
)
}
}
上面的代码有所省略,但基本上是对父子组件之前传递数据有基本的实现。注意,父组件的数据需要在子组件标签内用一个变量传入,这个变量将会放在子组件的props属性中。子组件调用这个变量时需要到props中调用。
- 子组件传出参数。一般来说,React的数据流是单向流动,即父组件的数据可以流入子组件,但是子组件的数据是不能向父组件传递或修改父组件的数据。但是子组件是可以通过自己的事件处理函数,手动触发父组件传递进来的回调函数,在回调函数上把数据通过参数传递进去。这样就可以让子组件当数据有变化的时候,父组件也可以根据变化进行响应处理:
代码2 子组件调用父组件回调函数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50// 父组件Book组件向子组件Search先传入getSearchInfo()方法
class Book extends Component {
constructor (props) {
super(props)
this.getSearchInfo = this.getSearchInfo.bind(this)
}
getSearchInfo (newKeyword) {
/* 省略代码 */
}
render () {
const { pullDownStatus, pullUpStatus } = this.props
return (
<Fragment>
<Search
searchInfo={this.getSearchInfo}
/>
</Fragment>
)
}
}
// 子组件在handleClick()方法中调用父组件传入的回调函数searchInfo(keyword),并把值传入
class Search extends Component {
constructor (props) {
super(props)
this.handleClick = this.handleClick.bind(this)
}
handleClick () {
const { searchInfo, keyword} = this.props
if (keyword !== '') {
searchInfo(keyword)
}
}
render () {
return (
<SearchWrapper>
<div>
<SearchInput />
<SearchButton
onClick={this.handleClick}
>搜索</SearchButton>
</div>
</SearchWrapper>
)
}
}
这里有个地方需要注意的。父组件方法getSearchInfo()
虽然是对象中的方法,但是它也有自己的作用域,所以正常来说它的this指向它自己的作用域,this.props就不存在了。所以需要在constructor()
中指定它的作用域为整个类,需要补上this.getSearchInfo = this.getSearchInfo.bind(this)
。
生命周期
我们来回头说一下组件的生命周期。组件的生命周期包括了初始化,第一次加载渲染,更新渲染跟卸载四个阶段。而React的生命周期函数主要分布在后面三个阶段。我们先每个阶段简单整理一下:
初始化
这个阶段主要还是调用了ES6中类的constructor()
。通过构造器完成对父类的继承,变量的初始化和函数指定作用域。这个阶段还没有调用生命周期函数。
第一次加载渲染
第一次加载渲染的过程会调用三个生命周期函数:
1.componentWillMount()
,这个函数将会在组件第一次加载渲染之前被调用。可以做一些组件渲染到页面前的操作。只调用一次。
2.render()
,这个函数就是负责创建虚拟DOM,此时所有数据都至少已经初始化了。
3.componentDidMount()
,这个函数是在组件第一次加载渲染完成之后马上被调用的,也只调用一次。这个函数可以调用ajax请求,返回的数据会在后面阶段更新组件状态,并触发重新渲染
更新渲染
第一次加载渲染完成之后到组件被卸载之前,组件中的state状态跟props数据还是可以改变。所以会调用下面几个生命周期函数:
1.componentWillReceiveProps(nextProps)
,在组件第一次加载渲染之后,如果props数据将要发生改变,那改变之前会先调用这个函数。函数的传参为props改变之后的值。
2.shouldComponentUpdate(nextProps,nextState)
,在组件props或state将发生改变,进行更新渲染之前会调用这个函数。函数的传参为props或者state改变之后的值。函数最后返回值一定是布尔值,表示是否重新渲染组件(true为重新渲染,false为阻止渲染)。这里可以判断props或state的值是否达到需要重新渲染组件的要求。这个函数的判断有助于提高页面性能,因为当父组件重新渲染时也会导致其所有子组件重新渲染,所以在组件中做这个判断可以阻止不必要的重新渲染。
3.componentWillUpdate (nextProps,nextState)
,在组件调用shouldComponentUpdate(nextProps,nextState)
并返回true
之后,进行更新渲染之前会调用这个函数。这里同样可以拿到nextProps
跟nextState
进行操作。
4.render()
,这里的渲染就是根据新更新的props
跟state
的值进行重新创建虚拟DOM,然后在根据diff算法比对新旧DOM数,找到有差异的最小DOM节点,并重新渲染。
5.componentDidUpdate(prevProps,prevState)
,在组件重新渲染之后会调用这个函数。函数的传参为props或者state改变之前的值。
组件卸载
当组件将不再被渲染到页面上时,组件将会被卸载。卸载前只有一个函数会被触发调用:
componentWillUnmount ()
,这个函数因为是在组件被卸载前被调用,所以很适合以下一些操作:
1.清除组件内的所有定时setTimeout
,setInterval
;
2.移除组件内的所有监听事件 removeEventListener
;
3.处理未完成的ajax请求。一般ajax请求之后都会修改state,导致会调用组件的setState()
,如果组件被卸载的时候ajax请求未完成,会导致请求完成后调用报错,所以可以在组件内设置状态值控制ajax请求完成后是否执行组件内setState()
,然后在componentWillUnmount ()
中修改状态值。
父子组件的生命周期
这里拓展一下,父子组件的生命周期被调用也是有顺序的。
在组件初次渲染阶段,会从父组件开始,先调用父组件的constructor()
构造函数、componentWillMount()
,然后接着子组件开始调用这两个函数,直到最底层子组件调用这两个函数。接着调用对底层子组件的componentDidMount()
,再一层层往上调用父组件的这个函数,直到最顶层父组件。
在组件的卸载阶段,则会先从最顶层父组件开始调用componentDidUnmount()
,直到最底层子组件。
父子组件之间的性能优化
其实在React中,父组件的数据变化,是会影响子组件进行重新渲染,倒是出现不必要的性能消耗。
前面有说到,可以通过shouldComponentUpdate(nextProps,nextState)
函数来判断是否需要重新渲染组件,但这样一来,每个组件都要手动判断重新渲染。每次只要出现父组件传来的数据,包括state状态的变化,都会调用这个函数,也会导致代码冗余。所以React还提供了一种处理方案。
React V15中就引入了React.PureComponent
。这个PureComponent
跟Component
用法上是一样的,只是组件继承了PureComponent
类后,组件会自动实现props
跟state
的浅比较。当组件的props
跟state
的类型都比较简单的时候,就可以直接使用PureComponent
。当如果props
跟state
是嵌套对象或数组时,浅比较将得不到预期的结果。
关于 React的内容就先把这些基础的先介绍到这里,主要还是要看文档,这里的就算是一些个人整理。下一篇将会介绍style-components插件。