当前位置: 移动技术网 > IT编程>开发语言>JavaScript > 详解React中的组件通信问题

详解React中的组件通信问题

2017年12月12日  | 移动技术网IT编程  | 我要评论

引入

本来我是没想过总结这些东西的,会感觉比较入门。但是之前同学去腾讯面试问到了这个问题(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 的定义,还是看具体的应用场景了。

另外本次的代码都放在, 可以 done 下来加深理解。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网