关于React组件之间如何优雅地传值的探讨
闲话不多说,开篇撸代码,你可以会看到类似如下的结构:
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等系列数据仓库
使用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
关于context的讲解,React文档中将它放在了进阶指引里面。具体地址在这里:https://reactjs.org/docs/context.html。主要的作用就是为了解决在本文开头列举出来的例子,为了不让props在每层的组件中都需要往下传递,而可以在任何一个子组件中拿到父组件中的属性。
但是,好用的东西往往也有副作用,官方也给出了几点不要使用context的建议,如下:
- 如果你想你的应用处于稳定状态,不要用
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的,但是在使用的基础上,必须知道它使用的场景,这样才能够防范于未然。
关于React组件之间如何优雅地传值的探讨的更多相关文章
- React组件之间通过Props传值的技巧(小案例,帮助体会理解props、state、受控组件和非受控组件等)
本文重要是根据react小书上的一个很简单的例子改编的,加上自己的学习理解,希望可以通过实际案例让大家对概念有更清晰的理解,当然也希望能一块学习. import React,{Component} f ...
- React 组件之间通信 All in One
React 组件之间通信 All in One 组件间通信 1. 父子组件之间通信 props 2. 兄弟组件之间通信 3. 跨多层级的组件之间通信 Context API https://react ...
- react 组件之间传值
谈及React时,就会想到一个很重要的思想,就是组件化思想.它将可以重用的部分进行组件化开发,形成一个个相对独立的组件,那么组件化后,你也会提出些疑问,组件与组件之间,将怎样进行信息的传递呢?下面来介 ...
- react组件之间传值方式
1.父向子(通过props传值) 2.父向更深层的子(通过context传值) 3.子向父(通过回调函数传值:在父组件中创建一个函数来接收子组件传过来的参数值,通过父组件将这个函数做为子组件的属性传递 ...
- 使用reflux进行react组件之间的通信
前言 组件之间为什么要通信?因为有依赖. 那么,作为React组件,怎么通信? React官网说, 进行 父-子 通信,可以直接pass props. 进行 子-父 通信,往父组件传给子组件的函数注入 ...
- react组件之间的几种通信情况
组件之间的几种通信情况 父组件向子组件通信 子组件向父组件通信 跨级组件通信 没有嵌套关系组件之间的通信 1,父组件向子组件传递 React数据流动是单向的,父组件向子组件通信也是最常见的;父组件通过 ...
- react 组件之间的通信
react推崇的是单向数据流,自上而下进行数据的传递,但是由下而上或者不在一条数据流上的组件之间的通信就会变的复杂.解决通信问题的方法很多,如果只是父子级关系,父级可以将一个回调函数当作属性传递给子级 ...
- react组件之间的组合方式
组合方式: 1/直接嵌套的方式 2/组件以变量的形式放置 3/可以通过props值,以变量的形式相当于作为参数传递父组件,然后进行组合 import React,{Component} from 'r ...
- 关于react组件之间的通信
才开始学react刚好到组件通信这一块,就简单的记录下组件间的通信方式:父到子:props.context,子到父:自定义事件.回调,兄弟组件:共父props传递.自定义事件import React, ...
随机推荐
- JavaScript系列----面向对象的JavaScript(1)
1.面向对象的编程 1.1.什么是面向对象编程 面向对象编程:即是把能够完成独立完成一部分功能的代码封装在一起,组成一个类. 举个例子来说: 这里有一把枪, 枪的种类很多,有步枪,机关枪,阻击枪... ...
- MongoDB入门系列(一):基础概念和安装
概述 MongoDB是目前非常流行的一种非关系型数据库,作为入门系列的第一篇本篇文章主要介绍Mongdb的基础概念知识包括命名规则.数据类型.功能以及安装等. 环境: OS:Windows Versi ...
- mac中利用brew实现多版本php共存以及任意切换
1.安装brew 参考链接:https://brew.sh/index_zh-cn.html 2.安装php56 brew install homebrew/php/php56 3.配置php56 因 ...
- AsciidocFX编辑器小贴士
I. AsciidocFX支持UML生成: 要生成UML,记得要下载GRAPHVIZ,并配置GRAPHVIZ_DOT环境变量,路径是Graphviz\bin\dot.exe. II. Asciidoc ...
- C#实现倒油算法
原题如下:12(a桶 满的 有12斤油)斤桶里 取出6斤油 有 另外有8斤(b桶)和5斤(c桶)两个空桶 让程序输出取出这6斤油的步骤 现在实现的算法可以配参数(定义有几个桶,初始有多少油,要得到多 ...
- [转载] 网络IO模型
转载自http://blog.csdn.net/zhoudaxia/article/details/8974779 同步(synchronous) IO和异步(asynchronous) IO,阻塞( ...
- Python 日志处理(二) 使用正则表达式处理Nginx 日志
使用正则表达式来处理Nginx 日志 一. 先对单行的日志进行分组正则匹配,返回匹配后的结果(字典格式): from datetime import datetime import re #单行日志 ...
- 一个可扩展的深度学习框架的Python实现(仿keras接口)
一个可扩展的深度学习框架的Python实现(仿keras接口) 动机 keras是一种非常优秀的深度学习框架,其具有较好的易用性,可扩展性.keras的接口设计非常优雅,使用起来非常方便.在这里,我将 ...
- 使用asp.net mvc引擎开发插件系统
一.前言 我心中的插件系统应该是像Nop那样(更牛逼的如Orchard,OSGI.NET),每个插件模块不只是一堆实现了某个业务接口的dll,然后采用反射或IOC技术来调用,而是一个完整的mvc小应用 ...
- Less命名空间
Less命名空间 当我们拥有了大量选择器的时候,特别是团队协同开发时,如何保证选择器之间重名问题?如果你是 java 程序员或 C++ 程序员,我猜你肯定会想到命名空间 Namespaces. Les ...