闲话不多说,开篇撸代码,你可以会看到类似如下的结构:
- 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 .
当一个组件嵌套了若干层子组件时,而想要在特定的组件中取得父组件的属性,就不得不将 props 一层一层地往下传,我这里只是简单的列举了 3 个子组件,而当子组件嵌套过深的时候,props 的维护将成噩梦级增长。因为在每一个子组件上你可能还会对传过来的 props 进行加工,以至于你最后都不确信你最初的 props 中将会有什么东西。
那么 React 中是否还有其他的方式来传递属性,从而改善这种层层传递式的属性传递。答案肯定是有的,主要还有以下两种形式:
使用 Redux 相当于在全局维护了整个应用数据的仓库,当数据改变的时候,我们只需要去改变这个全局的数据仓库就可以了。类似这样的:
- var state = {
- a: 1
- };
- // index1.js
- state.a = 2;
- // index2.js
- console.log(state.a); // 2
当然这只是一种非常简单的形式解析,Reudx 中的实现逻辑远比这个要复杂得多,有兴趣可以去深入了解,或者看我之前的文章: 用 react+redux 编写一个页面小 demo 以及 react 脚手架改造 ,下面大致列举下代码:
- // 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。
这只是一个任一组件的大致演示,这就意味着你可以在任何组件中来改变 store 中的状态。关于什么时候引入 redux 我觉得也要根据项目来,如果一个项目中大多数时候只是需要跟组件内部打交道,那么引入 redux 反而造成了一种资源浪费,更多地引来的是学习成本和维护成本,因此并不是说所有的项目我都一定要引入 redux。
关于 context 的讲解,React 文档中将它放在了进阶指引里面。具体地址在这里: https://reactjs.org/docs/context.html 。主要的作用就是为了解决在本文开头列举出来的例子,为了不让 props 在每层的组件中都需要往下传递,而可以在任何一个子组件中拿到父组件中的属性。
但是,好用的东西往往也有副作用,官方也给出了几点不要使用 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
中返回 true 的时候,一切都是那么地正常,但是当我返回 false 的时候,颜色将不再发生变化。
- shouldComponentUpdate
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 的,但是在使用的基础上,必须知道它使用的场景,这样才能够防范于未然。
来源: https://www.cnblogs.com/rynxiao/p/8109546.html