这里有新鲜出炉的精品教程,程序狗速度看过来!
本篇文章中主要介绍了详解 React 中的组件通信问题,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
引入
本来我是没想过总结这些东西的,会感觉比较入门。但是之前同学去腾讯面试问到了这个问题 (react 或 vue 的组件通信),我帮他整理,顺便写 demo 的过程中,会有一些新的体会,多总结还是有利于进步的呀。
父子组件
父 → 子
parent 组件传给 child 组件,符合 react 的单向数据流理念,自上到下传递 props。
- // 父组件
- class Parent extends Component {
- constructor() {
- super();
- this.state = {
- value: '',
- }
- }
- handleChange = e = >{
- this.value = e.target.value;
- }
- handleClick = () = >{
- this.setState({
- value: this.value,
- })
- }
- render() {
- return ( < div > 我是parent < input onChange = {
- this.handleChange
- }
- />
- <div className="button" onClick={this.handleClick}>通知</div > <div > <Child value = {
- this.state.value
- }
- />
- </div > </div>
- );
- }
- }/
- // 子组件
- class Child extends Component {
- render() {
- const {
- value
- } = this.props;
- return ( < div > 我是Child,得到传下来的值: {
- value
- } < /div>
- );
- }
- }/
父组件做的就是定义好 state ,定义好事件函数,input onChange 的时候,去缓存 value 值,然后点击 button 的时候,改变 state , 子组件只负责展示 value 。
子 → 父
child 组件通知 parent 组件, 主要是依靠 parent 传下来的 callback 函数执行,改变 parent 组件的状态,或者把 child 自己的 state 通知 parent 。分两种情况:
state 定义在 parent 组件
- // parent
- class Parent extends Component {
- constructor() {
- super();
- this.state = {
- value: '',
- }
- }
- setValue = value = >{
- this.setState({
- value,
- })
- }
- render() {
- return ( < div > <div > 我是parent, Value是: {
- this.state.value
- } < /div>
- <Child setValue={this.setValue} / > </div>
- );
- }
- }/
- class Child extends Component {
- handleChange = e = >{
- this.value = e.target.value;
- }
- handleClick = () = >{
- const {
- setValue
- } = this.props;
- setValue(this.value);
- }
- render() {
- return ( < div > 我是Child < div className = "card" > state定义在parent < input onChange = {
- this.handleChange
- }
- />
- <div className="button" onClick={this.handleClick}>通知</div > </div>
- </div > );
- }
- }
parent 组件把改变 state 的 setValue 函数传给 child ,child 组件自己处理内部的状态(这里是表单的 value 值),当 child 组件分发消息的时候, 执行 parent 的 setValue 函数,从而改变了 parent 的 state,state 发生变化, parent 组件执行 re-render 。
state 定义在 child 组件
- // parent
- class Parent extends Component {
- onChange = value => {
- console.log(value, '来自 child 的 value 变化');
- }
- render() {
- return (
- <div>
- <div>我是parent
- <Child onChange={this.onChange} />
- </div>
- );
- }
- }
- class Child extends Component {
- constructor() {
- super();
- this.state = {
- childValue: ''
- }
- }
- childValChange = e = >{
- this.childVal = e.target.value;
- }
- childValDispatch = () = >{
- const {
- onChange
- } = this.props;
- this.setState({
- childValue: this.childVal,
- },
- () = >{
- onChange(this.state.childValue)
- })
- }
- render() {
- return ( < div > 我是Child < div className = "card" > state定义在child < input onChange = {
- this.childValChange
- }
- />
- <div className="button" onClick={this.childValDispatch}>通知</div > </div>
- </div > );
- }
- }
有时候 state 是需要定义在 child 组件的,比如弹窗, CheckBox 这种开关性质的,逻辑是重复的,state 定义在组件内部更好维护, 复用性更好。但是 child 的 state 是需要告知我的 parent 组件的, 同样还是执行 parent 传下来的 change 函数。
兄弟组件
有时候可能出现页面中的某两部分通信,比如省市的级联选择,点击 button 改变颜色等等,组件并不是父子级,没有嵌套关系的时候。这种时候通常是依赖共有的顶级 Container 处理或者第三方的状态管理器。其实原理都是相通的,兄弟 A 的 value 发生变化,分发的时候把 value 值告诉一个中间者 C ,C 会自动告知 B,实现 B 的自动 render 。
利用共有的 Container
- // container
- class Container extends Component {
- constructor() {
- super();
- this.state = {
- value: '',
- }
- }
- setValue = value = >{
- this.setState({
- value,
- })
- }
- render() {
- return ( < div > <A setValue = {
- this.setValue
- }
- />
- <B value={this.state.value} / > </div>
- );
- }
- }/
- // 兄弟A
- class A extends Component {
- handleChange = (e) = >{
- this.value = e.target.value;
- }
- handleClick = () = >{
- const {
- setValue
- } = this.props;
- setValue(this.value);
- }
- render() {
- return ( < div className = "card" > 我是Brother A, <input onChange = {
- this.handleChange
- }
- />
- <div className="button" onClick={this.handleClick}>通知</div > </div>
- )
- }
- }/
- // 兄弟B
- const B = props => (
- <div className="card">
- 我是Brother B, value是:
- {props.value}
- </div>
- );
- export default B;
组件 A 中的表单 value 值,告知了父级 Container 组件(通过 setValue 函数改变 state),组件 B 依赖于 Container 传下来的 state,会做出同步更新。这里的中间者是 Container。
利用 Context
上面的方式,如果嵌套少还可以,如果嵌套特别多,比如一级导航栏下的二级导航栏下的某个按钮,要改变页面中 content 区域的 table 里的某个列的值... 他们同属于一个 page 。这样传递 props 就会很痛苦,每一层组件都要传递一次。
// 顶级公共组件
class Context extends Component {
- constructor() {
- super();
- this.state = {
- value: '',
- };
- }
- setValue = value => {
- this.setState({
- value,
- })
- }
- getChildContext() { // 必需
- return {
- value: this.state.value,
- setValue: this.setValue,
- };
- }
- render() {
- return (
- <div>
- <AParent />
- <BParent />
- </div>
- );
- }
- }
- // 必需
- Context.childContextTypes = {
- value: PropTypes.string,
- setValue: PropTypes.func,
- };
- // A 的 parent
- class AParent extends Component {
- render() {
- return ( < div className = "card" > <A / ></div>
- );
- }
- }
- / / A class A extends Component {
- handleChange = (e) = >{
- this.value = e.target.value;
- }
- handleClick = () = >{
- const {
- setValue
- } = this.context;
- setValue(this.value);
- }
- render() {
- return ( < div > 我是parentA下的A, <input onChange = {
- this.handleChange
- }
- />
- <div className="button" onClick={this.handleClick}>通知</div > </div>
- );
- }
- }
- / / 必需A.contextTypes = {
- setValue: PropTypes.func,
- };
- // B 的 parent
- class BParent extends Component {
- render() {
- return (
- <div className="card">
- <B />
- </div>
- );
- }
- }
- // B
- class B extends Component {
- render() {
- return (
- <div>
- 我是parentB 下的 B, value是:
- {this.context.value}
- </div>
- );
- }
- }
- B.contextTypes = {
- value: PropTypes.string,
- };
组件 A 仍是 消息的发送者,组件 B 是接收者, 中间者是 Context 公有 Container 组件。context 是官方文档的一个 API ,通过 getChildContext 函数定义 context 中的值,并且还要求 childContextTypes 是必需的。这样属于这个 Container 组件的子组件,通过 this.context 就可以取到定义的值,并且起到跟 state 同样的效果。中间者其实还是 Container,只不过利用了上下文这样的 API ,省去了 props 的传递。另外:这个功能是实验性的,未来可能会有所改动。
发布订阅
这种一个地方发送消息,另一个地方接收做出变化的需求,很容易想到的就是观察者模式了。具体的实现会有很多种,这里我们自己写了一个 EventEmitter 的类(其实就是仿照 node 中的 EventEmitter 类),如果不了解观察者,可以看我的另一篇文章 观察者模式 。
- // 发布订阅类
- class EventEmitter {
- _event = {}
- // on 函数用于绑定
- on(eventName, handle) {
- let listeners = this._event[eventName];
- if (!listeners || !listeners.length) {
- this._event[eventName] = [handle];
- return;
- }
- listeners.push(handle);
- }
- // off 用于移除
- off(eventName, handle) {
- let listeners = this._event[eventName];
- this._event[eventName] = listeners.filter(l = >l !== handle);
- }
- // emit 用于分发消息
- emit(eventName, ...args) {
- const listeners = this._event[eventName];
- if (listeners && listeners.length) {
- for (const l of listeners) {
- l(...args);
- }
- }
- }
- }
- const event = new EventEmitter;
- export {
- event
- };
- // Container
- import A from './a';
- import B from './b';
- const Listener = () => {
- return (
- <div>
- <A />
- <B />
- </div>
- );
- };
- export default Listener;
- // 兄弟组件 A
- import { event } from './eventEmitter';
- class A extends Component {
- handleChange = e => {
- this.value = e.target.value;
- }
- handleClick = () => {
- event.emit('dispatch', this.value);
- }
- render() {
- return (
- <div className="card">
- 我是Brother A, <input onChange={this.handleChange} />
- <div className="button" onClick={this.handleClick}>通知</div>
- </div>
- )
- }
- }
- // 兄弟组件 B
- import {
- event
- }
- from './eventEmitter';
- class B extends Component {
- state = {
- value: ''
- }
- componentDidMount() {
- event.on('dispatch', this.valueChange);
- }
- componentWillUnmount() {
- event.off('dispatch', this.valueChange);
- }
- valueChange = value = >{
- this.setState({
- value,
- })
- }
- render() {
- return ( < div className = "card" > 我是Brother B, value是: {
- this.state.value
- } < /div>
- );
- }
- }/
仍然是组件 A 用于分发消息,组件 B 去接收消息。这里的中间者其实就是 event 对象。需要接收消息的 B 去订阅 dispatch 事件,并把回调函数 valueChange 传入,另外 B 定义了自己的 state,方便得到 value 值的时候自动渲染。组件 A 其实就是把内部的表单 value 在点击的时候分发,发布事件,从而 B 中的 valueChange 执行,改变 state。这种方式比较方便,也更直观,不需要借助 Container 组件去实现,省去了很多逻辑。
Redux || Mobx
Redux 或者 Mobx 是第三方的状态管理器,是这里我们通信的中间者。大型项目最直接的就是上库... 更方便,更不容易出错。 但其实小项目就没什么必要了。东西比较多,这里不再阐述它们的实现和做了什么。
总结
react 特殊的自上而下的单向数据流,和 state 的特性,造就以这样的思想实现组件通信。除去发布订阅和 Redux 等,其他的都是 props 自上而下传递的理念,子组件需要的总是通过父组件传递下来的,关于 state 的定义,还是看具体的应用场景了。
另外本次的代码都放在 https://github.com/sunyongjian/rc-communication-demo, 可以 done 下来加深理解。
来源: http://www.phperz.com/article/17/0821/343919.html