在项目开发的过程中, 遇到了一个问题:
父组件请求后台数据, 收到后将数据以 props 传给子组件, 子组件根据收到数据的不同, 显示不同的内容, 同时, 子组件自身可以根据 click 操作改变根据父组件的数据显示的内容.
因此, 子组件收到父组件的 props 传值后, 由于 props 值不能修改, 因此子组件需要将该 props 值放入 state, 子组件根据自身 click 操作改变 state, 进而改变组件显示的内容.
而父组件传过来的值, 子组件在 componentDidMount 中无法获取, 可以在 render 中获取. 但是若将获取值的部分写在 render 中, 会导致页面每次更新都从父组件拿值, 子组件的 click 操作修改值无法起作用.
子组件代码如下:
- class child extends PureComponent {
- static propTypes = {
- value: PropTypes.bool.isRequired
- };
- constructor(props) {
- super( props);
- this.state = {
- value: false
- };
- }
- changeValue = () => {
- this.setState({
- value: false
- });
- }
- render() {
- this.state = {
- value: this.props.value
- };
- return (
- <div style={{ display: value ? 'block':'none' }}>
- <img src={src} alt="" />
- <div className={styles.value}>这是父组件的传值:{value}</div>
- <div className={styles['icon-close']} onClick={this.changeValue}>
- <img src={close} alt="" />
- </div>
- </div>
- );
- }
- }
- export default Child;
解决方法 1: 将 click 的 close 方法写在 render 中
但该写法不是很规范, render() 函数应该是纯粹的, 也就是说该函数不修改组件 state, 每次调用都返回相同的结果, 不读写 DOM 信息, 也不和浏览器交互(例如通过使用 setTimeout).
- class child extends PureComponent {
- static propTypes = {
- value: PropTypes.bool.isRequired
- };
- constructor(props) {
- super( props);
- this.state = {
- value: false
- };
- }
- render() {
- this.state = {
- value: this.props.value
- };
- changeValue = () => {
- this.setState({
- value: false
- });
- }
- return (
- <div style={{ display: value ? 'block':'none' }}>
- <img src={src} alt="" />
- <div className={styles.value}>这是父组件的传值:{value}</div>
- <div className={styles['icon-close']} onClick={changeValue}>
- <img src={close} alt="" />
- </div>
- </div>
- );
- }
- }
- export default Child;
解决方法 2: 使用 componentWillReceiveProps
- class child extends PureComponent {
- static propTypes = {
- value: PropTypes.bool.isRequired
- };
- constructor(props) {
- super( props);
- this.state = {
- value: false
- };
- }
- componentWillReceiveProps(nextProps) {
- this.setState({
- value: nextProps.value
- });
- }
- changeValue = () => {
- this.setState({
- value: false
- });
- }
- render() {
- return (
- <div style={{ display: value ? 'block':'none' }}>
- <img src={src} alt="" />
- <div className={styles.value}>这是父组件的传值:{value}</div>
- <div className={styles['icon-close']} onClick={this.changeValue}>
- <img src={close} alt="" />
- </div>
- </div>
- );
- }
- }
- export default Child;
总结:
react 组件的生命周期
- componentWillMount
- componentDidMount
- componentWillReceiveProps
- shouldComponentUpdate
- componentWillUpdate
- componentDidUpdate
- componentWillUnmount
1. componentWillMount 组件将要挂载
组件刚经历 constructor, 初始完数据
在初始化渲染执行之前立刻调用, 服务器端和客户端都只调用一次
组件还未进入 render, 组件还未渲染完成, dom 还未渲染
如果在这个方法内调用 setState,render() 将会感知到更新后的 state, 将会执行仅一次, 尽管 state 改变了.
2. componentDidMount 组件渲染完成
在初始化渲染执行之后立刻调用一次, 仅客户端有效(服务器端不会调用)
此时 dom 节点已经生成, 可以在这里调用 ajax 请求, 返回数据 setState 后组件会重新渲染
在生命周期中的这个时间点, 组件拥有一个 DOM 展现, 你可以通过 this.getDOMNode() 来获取相应 DOM 节点
可以在这个方法中调用 setTimeout, setInterval 或者发送 AJAX 请求等操作(防止异步操作阻塞 UI)
3.componentWillReceiveProps (nextProps)
在组件接收到一个新的 prop (更新后)时被调用. 这个方法在初始化 render 时不会被调用.
在接受父组件改变后的 props 需要重新渲染组件时用到的比较多
用此函数可以作为 react 在 prop 传入之后, render() 渲染之前更新 state 的机会.
在该函数中调用 this.setState() 将不会引起第二次渲染.
通过对比 nextProps 和 this.props, 将 nextProps setState 为当前组件的 state, 从而重新渲染组件
- componentWillReceiveProps (nextProps) {
- nextProps.openNotice !== this.props.openNotice && this.setState({
- openNotice:nextProps.openNotice
- },() => {
- console.log(this.state.openNotice:nextProps) // 将 state 更新为 nextProps, 在 setState 的第二个参数 (回调) 可以打印出新的 state
- })
- }
- 4. shouldComponentUpdate(nextProps,nextState)
唯一用于控制组件重新渲染的生命周期
由于在 react 中, setState 以后, state 发生变化, 组件会进入重新渲染的流程(暂时这么理解, 其实 setState 以后有些情况并不会重新渲染, 比如数组引用不变).react 父组件的重新渲染会导致其所有子组件的重新渲染, 这个时候其实我们是不需要所有子组件都跟着重新渲染的, 因此需要在子组件的该生命周期中做判断. 在这里 return false 可以阻止组件的更新
如果 shouldComponentUpdate 返回 false, 则 render() 将不会执行, 直到下一次 state 改变.(另外, componentWillUpdate 和 componentDidUpdate 也不会被调用.)
在接收到新的 props 或者 state, 将要渲染之前调用. 该方法在初始化渲染的时候不会调用, 在使用 forceUpdate 方法的时候也不会.
默认情况下, shouldComponentUpdate 总会返回 true, 在 state 改变的时候避免细微的 bug, 但是如果总是小心地把 state 当做不可变的, 在 render() 中只从 props 和 state 读取值, 此时你可以覆盖 shouldComponentUpdate 方法, 实现新老 props 和 state 的比对逻辑.
如果性能是个瓶颈, 尤其是有几十个甚至上百个组件的时候, 使用 shouldComponentUpdate 可以提升应用的性能.
5. componentWillUpdate (nextProps,nextState)
在组件接收到新的 props 或者 state 但还没有 render 时被调用. 在初始化时不会被调用.
shouldComponentUpdate 返回 true 以后, 组件进入重新渲染的流程, 进入 componentWillUpdate, 这里同样可以拿到 nextProps 和 nextState
你不能在该方法中使用 this.setState(). 如果需要更新 state 来响应某个 prop 的改变, 可使用 componentWillReceiveProps.
6. componentDidUpdate(prevProps,prevState)
在组件完成更新后立即调用. 在初始化时不会被调用.
组件更新完毕后, react 只会在第一次初始化成功会进入 componentDidmount, 之后每次重新渲染后都会进入这个生命周期
在组件的更新已经同步到 DOM 中之后立刻被调用.
这里可以拿到 prevProps 和 prevState, 即更新前的 props 和 state.
使用该方法可以在组件更新之后操作 DOM 元素.
为了兼容 v0.9,DOM 节点会作为最后一个参数传入. 如果使用这个方法, 你仍然可以使用 this.getDOMNode() 来访问 DOM 节点.
7.componentWillUnmount()
在组件从 DOM 中移除的时候立刻被调用.
在该方法中执行任何必要的清理, 比如无效的定时器, 或者清除在 componentDidMount 中创建的 DOM 元素, 移除所有组建中的监听 removeEventListener
除此之外,
8.constructor
constructor 参数接受两个参数 props,context, 可以获取到父组件传下来的的 props,context,
如果你想在 constructor 构造函数内部使用 props 或 context, 则需要传入, 并传入 super 对象.
只要组件存在 constructor, 就必要要写 super, 否则 this 指向会错误
9. render 函数
render 函数会插入 jsx 生成的 dom 结构, react 会生成一份虚拟 dom 树,
在每一次组件更新时, 在此 react 会通过其 diff 算法比较更新前后的新旧 DOM 树, 比较以后, 找到最小的有差异的 DOM 节点, 并重新渲染
react16 中 render 函数允许返回一个数组, 单个字符串等, 不在只限制为一个顶级 DOM 节点, 可以减少很多不必要的 div
组件生命周期的执行顺序:
在 react 的组件挂载及 render 过程中, 最底层的子组件是最先完成挂载及更新的.
constructor()构造函数, componentWillMount 执行顺序: 顶层父组件 ->子组件 ->子组件 ->底层子组件
render,componentDidMount 顺序: 底层子组件 ->子组件 ->子组件 ->顶层父组件
以下是 http://www.runoob.com/ 菜鸟教程上面的一个实例:
- class Button extends React.Component {
- constructor(props) {
- super(props);
- this.state = {data: 0};
- this.setNewNumber = this.setNewNumber.bind(this);
- }
- setNewNumber() {
- this.setState({data: this.state.data + 1})
- }
- render() {
- return (
- <div>
- <button onClick = {this.setNewNumber}>INCREMENT</button>
- <Content myNumber = {this.state.data}></Content>
- </div>
- );
- }
- }
- class Content extends React.Component {
- componentWillMount() {
- console.log('Component WILL MOUNT!')
- }
- componentDidMount() {
- console.log('Component DID MOUNT!')
- }
- componentWillReceiveProps(newProps) {
- console.log('Component WILL RECEIVE PROPS!')
- }
- shouldComponentUpdate(newProps, newState) {
- return true;
- }
- componentWillUpdate(nextProps, nextState) {
- console.log('Component WILL UPDATE!');
- }
- componentDidUpdate(prevProps, prevState) {
- console.log('Component DID UPDATE!')
- }
- componentWillUnmount() {
- console.log('Component WILL UNMOUNT!')
- }
- render() {
- return (
- <div>
- <h3>{this.props.myNumber}</h3>
- </div>
- );
- }
- }
- ReactDOM.render(
- <div>
- <Button />
- </div>,
- document.getElementById('example')
- );
运行结果:
(控制台 info)
点击按钮:
参考: https://www.jianshu.com/p/c9bc994933d5
- https://www.jianshu.com/p/ee122bb5b14b
- http://www.runoob.com/react/react-component-life-cycle.html
- https://blog.csdn.net/limm33/article/details/50942808
来源: https://www.cnblogs.com/chaoxiZ/p/9578878.html