闲话很少说,开篇撸代码,你能够会看到相似以下的结构:javascript
import React, { Component } from 'react'; // 父组件 class Parent extends Component { constructor() { super(); this.state = { color: 'red' }; } render() { return <Child1 { ...this.props } /> } } // 子组件1 const Child1 = props => { return <Child2 { ...props } /> } // 子组件2 const Child2 = props => { return <Child3 { ...props } /> } // 子组件3 const Child3 = props => { return <div style={{ color: props.color }}>Red</div> }
See the Pen react props by 糊一笑 (@rynxiao) on CodePen.html
当一个组件嵌套了若干层子组件时,而想要在特定的组件中取得父组件的属性,就不得不将props
一层一层地往下传,我这里只是简单的列举了3个子组件,而当子组件嵌套过深的时候,props
的维护将成噩梦级增加。由于在每个子组件上你可能还会对传过来的props
进行加工,以致于你最后都不确信你最初的props
中将会有什么东西。java
那么React
中是否还有其余的方式来传递属性,从而改善这种层层传递式的属性传递。答案确定是有的,主要还有如下两种形式:react
使用Redux
至关于在全局维护了整个应用数据的仓库,当数据改变的时候,咱们只须要去改变这个全局的数据仓库就能够了。相似这样的:git
var state = { a: 1 }; // index1.js state.a = 2; // index2.js console.log(state.a); // 2
固然这只是一种很是简单的形式解析,Reudx
中的实现逻辑远比这个要复杂得多,有兴趣能够去深刻了解,或者看我以前的文章:用react+redux编写一个页面小demo以及react脚手架改造,下面大体列举下代码:github
// actions.js function getA() { return { type: GET_DATA_A }; } // reducer.js const state = { a: 1 }; function reducer(state, action) { case GET_DATA_A: state.a = 2; return state; default: return state; } module.exports = reducer; // Test.js class Test extends React.Component { constructor() { super(); } componentDidMount() { this.props.getA(); } } export default connect(state => { return { a: state.reducer.a } }, dispatch => { return { getA: dispatch => dispatch(getA()) } })(Test);
这样当在Test
中的componentDidMount
中调用了getA()
以后,就会发送一个action
去改变store
中的状态,此时的a已经由原先的1变成了2。redux
这只是一个任一组件的大体演示,这就意味着你能够在任何组件中来改变store
中的状态。关于何时引入redux
我以为也要根据项目来,若是一个项目中大多数时候只是须要跟组件内部打交道,那么引入redux
反而形成了一种资源浪费,更多地引来的是学习成本和维护成本,所以并非说全部的项目我都必定要引入redux
。函数
关于context
的讲解,React
文档中将它放在了进阶指引里面。具体地址在这里:https://reactjs.org/docs/context.html。主要的做用就是为了解决在本文开头列举出来的例子,为了避免让props
在每层的组件中都须要往下传递,而能够在任何一个子组件中拿到父组件中的属性。学习
可是,好用的东西每每也有反作用,官方也给出了几点不要使用context
的建议,以下:this
context
Redux
或者MobX
等状态管理库,不要用context
React
开发者,不要用context
鉴于以上三种状况,官方更好的建议是老老实实使用props
和state
。
下面主要大体讲一下context
怎么用,其实在官网中的例子已经十分清晰了,咱们能够将最开始的例子改一下,使用context
以后是这样的:
class Parent extends React.Component { constructor(props) { super(props); this.state = { color: 'red' }; } getChildContext() { return { color: this.state.color } } render() { return <Child1 /> } } const Child1 = () => { return <Child2 /> } const Child2 = () => { return <Child3 /> } const Child3 = ({ children }, context) => { console.log('context', context); return <div style={{ color: context.color }}>Red</div> } Parent.childContextTypes = { color: PropTypes.string }; Child3.contextTypes = { color: PropTypes.string }; ReactDOM.render(<Parent />, document.getElementById('container'));
能够看到,在子组件中,全部的{ ...props }
都不须要再写,只须要在Parent
中定义childContextTypes
的属性类型,以及定义getChildContext
钩子函数,而后再特定的子组件中使用contextTypes
接收便可。
See the Pen react context by 糊一笑 (@rynxiao) on CodePen.
这样作貌似十分简单,可是你可能会遇到这样的问题:当改变了context
中的属性,可是因为并无影响父组件中上一层的中间组件的变化,那么上一层的中间组件并不会渲染,这样即便改变了context
中的数据,你指望改变的子组件中并不必定可以发生变化,例如咱们在上面的例子中再来改变一下:
// Parent render() { return ( <div className="test"> <button onClick={ () => this.setState({ color: 'green' }) }>change color to green</button> <Child1 /> </div> ) }
增长一个按钮来改变state
中的颜色
// Child2 class Child2 extends React.Component { shouldComponentUpdate() { return true; } render() { return <Child3 /> } }
增长shouldComponentUpdate
来决定这个组件是否渲染。当我在shouldComponentUpdate
中返回true
的时候,一切都是那么地正常,可是当我返回false
的时候,颜色将再也不发生变化。
See the Pen react context problem by 糊一笑 (@rynxiao) on CodePen.
既然发生了这样的状况,那是否意味着咱们不能再用context
,没有绝对的事情,在这篇文章How to safely use React context中给出了一个解决方案,咱们再将上面的例子改造一下:
// 从新定义一个发布对象,每当颜色变化的时候就会发布新的颜色信息 // 这样在订阅了颜色改变的子组件中就能够收到相关的颜色变化讯息了 class Theme { constructor(color) { this.color = color; this.subscriptions = []; } setColor(color) { this.color = color; this.subscriptions.forEach(f => f()); } subscribe(f) { this.subscriptions.push(f) } } class Parent extends React.Component { constructor(props) { super(props); this.state = { theme: new Theme('red') }; this.changeColor = this.changeColor.bind(this) } getChildContext() { return { theme: this.state.theme } } changeColor() { this.state.theme.setColor('green'); } render() { return ( <div className="test"> <button onClick={ this.changeColor }>change color to green</button> <Child1 /> </div> ) } } const Child1 = () => { return <Child2 /> } class Child2 extends React.Component { shouldComponentUpdate() { return false; } render() { return <Child3 /> } } // 子组件中订阅颜色改变的信息 // 调用forceUpdate强制本身从新渲染 class Child3 extends React.Component { componentDidMount() { this.context.theme.subscribe(() => this.forceUpdate()); } render() { return <div style={{ color: this.context.theme.color }}>Red</div> } } Parent.childContextTypes = { theme: PropTypes.object }; Child3.contextTypes = { theme: PropTypes.object }; ReactDOM.render(<Parent />, document.getElementById('container'));
看上面的例子,其实就是一个订阅发布者模式,一旦父组件颜色发生了改变,我就给子组件发送消息,强制调用子组件中的forceUpdate
进行渲染。
See the Pen react context problem resolve by 糊一笑 (@rynxiao) on CodePen.
但在开发中,通常是不会推荐使用forceUpdate
这个方法的,由于你改变的有时候并非仅仅一个状态,但状态改变的数量只有一个,可是又会引发其余属性的渲染,这样会变得得不偿失。
另外基于此原理实现的有一个库: MobX,有兴趣的能够本身去了解。
整体建议是:能别用context
就别用,一切须要在本身的掌控中才可使用。
这是本身在使用React
时的一些总结,本意是朝着偷懒的方向上去了解context
的,可是在使用的基础上,必须知道它使用的场景,这样才可以防范于未然。