本篇文章主要简单介绍一下关于组件间通信的几种方式,文中出现具体代码请查看 Git地址。希望可以对大家有一点帮助,文中如有错误,欢迎指正。
一、父子组件之间通信
在React中父子组件间的通讯主要是通过props进行,子组件接收父组件传过来的props值进行更新,而子组件向父组件通信主要是通过父组件传递过来的回调函数,从而改变状态,不多说,看代码。
父组件
import React, { Component } from 'react';
import Child from './child';
class Parent extends Component {
state = {
name: 'lj'
}
changeName = name => {
this.setState({
name
});
}
render() {
const { name } = this.state;
return (
<div style={{ border: '1px solid' }}>
parent
<Child name={name} changeName={this.changeName}/> // 进行值传递
</div>
);
}
}
export default Parent;
子组件
import React from 'react';
function Child(props) {
const {name, changeName} = props; // 接收值和回调函数
return (
<div style={{ border: '1px solid red', margin: 100 }}>
{ name }
<button onClick={() => changeName('hd')}>按钮</button> // 触发回调函数
</div>
);
}
export default Child;
二、组件间通信
1.利用共有的父级
当child1组件value改变之后 通过回调函数changeValue 通知父组件Parent,父组件Parent接收到之后再传递给子组件child2,父组件Parent相当于一个中间件。
1>父组件代码
import React, { Component } from 'react';
import Child1 from './Child1';
import Child2 from './Child2';
class Parent extends Component {
state = {
value: ''
}
changeValue = value => {
this.setState({
value
});
}
render() {
const { value } = this.state;
return (
<div style={{ border: '1px solid' }}>
<Child1 changeValue={ this.changeValue }/>
<Child2 value={value}/>
</div>
);
}
}
export default Parent;
2>子组件1代码
import React, { Component } from 'react';
class Child1 extends Component {
state = {
value: ''
}
inputChange = e => {
this.setState({
value: e.target.value
});
}
toChild2 = () => {
const { changeValue } = this.props;
const { value } = this.state;
changeValue(value);
}
render() {
const { value } = this.state;
return (
<div style={{ background: 'red', margin: '100px 0px' }}>
我是child1
<input type="text" onChange={this.inputChange} value={value}/>
<button onClick={this.toChild2}>通知B</button>
</div>
)
}
}
export default Child1;
3>子组件2代码
import React, { Component } from 'react';
class Child2 extends Component {
render() {
const {value} = this.props;
return (
<div style={{ background: 'yellow' }}>
我是child2,这是child1传过来的值{value}
</div>
)
}
}
export default Child2;
2.利用context
关于context, 这里分成3部分进行介绍,分别是老版本(16版本以下)、新版本(16版本以上)、Hook。
2.1 老版本
getChildContext 根组件中声明,一个函数,返回一个对象,就是context。
childContextTypes 根组件中声明,指定context的结构类型,如不指定,会产生错误。
contextTypes 子孙组件中声明,指定要接收的context的结构类型,可以只是context的一部分结构。contextTypes 没有定义,context将是一个空对象。
this.context 在子孙组件中通过此来获取上下文。
1>父组件
import React, { Component } from 'react';
import Child1 from './Child1';
import Child2 from './Child2';
import PropTypes from 'prop-types';
class Parent extends Component {
...
getChildContext() {
return {
value: this.state.value,
changeValue: this.changeValue
}
}
render() {
return (
<div style={{ border: '1px solid' }}>
<Child1 />
<Child2 />
</div>
);
}
}
Parent.childContextTypes = { // 指定context结构类型
value: PropTypes.string,
changeValue: PropTypes.func,
}
export default Parent;
2>子组件1
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class Child1 extends Component {
...
toChild2 = () => {
const { changeValue } = this.context;
const { value } = this.state;
changeValue(value);
}
render() {
const { value } = this.state;
return (
<div style={{ background: 'red', margin: '100px 0px' }}>
我是child1
<input type="text" onChange={this.inputChange} value={value}/>
<button onClick={this.toChild2}>通知B</button>
</div>
)
}
}
Child1.contextTypes = { // 指定需要获取的context属性
changeValue: PropTypes.func,
};
export default Child1;
3>子组件2
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class Child2 extends Component {
render() {
const {value} = this.context;
return (
<div style={{ background: 'yellow' }}>
我是child2,这是child1传过来的值{value}
</div>
)
}
}
Child2.contextTypes = {
value: PropTypes.string,
};
export default Child2;
2.2新版本
新版本的React context使用了Provider和Customer模式,和react-redux的模式非常像。在顶层的Provider中传入value, 在子孙级的Consumer中获取该值,并且能够传递函数,用来修改context。
1>父组件
import React, { Component } from 'react';
import Child1 from './Child1';
import Child2 from './Child2';
export const Context = React.createContext();
class Parent extends Component {
...
render() {
return (
<Context.Provider value={{ value: this.state.value, changeValue: this.changeValue }}>
<div style={{ border: '1px solid' }}>
<Child1 />
<Child2 />
</div>
</Context.Provider >
);
}
}
export default Parent;
2>子组件1
import React, { Component } from 'react';
import { Context } from './index';
class Child1 extends Component {
...
toChild2 = (fn) => {
const { value } = this.state;
fn(value);
}
render() {
return (
<Context.Consumer>
{
data => {
return (
<div style={{ background: 'red', margin: '100px 0px' }}>
我是child1
<input type="text" onChange={this.inputChange} value={this.state.value} />
<button onClick={() => this.toChild2(data.changeValue)}>通知B</button>
</div>
)
}
}
</Context.Consumer>
)
}
}
export default Child1;
3>子组件2
import React, { Component } from 'react';
import { Context } from './index';
class Child2 extends Component {
render() {
return (
<Context.Consumer>
{
data => {
return (
<div style={{ background: 'yellow' }}>
我是child2,这是child1传过来的值{data.value}
</div>
)
}
}
</Context.Consumer>
)
}
}
export default Child2;
2.3 Hook写法
通过createContext来 进行兄弟组件通信,hook的方式主要是通过useContext,写法基本和2.2的区别不是很大,在这里简单实现以下,另外在这里也说明一下如何使用useImperativeHandle完成父组件调用子组件的方式。
1> 父组件
import React, { createContext, useState } from 'react';
import Child1 from './Child1';
import Child2 from './Child2';
export const Context = createContext();
function Parent() {
const [value, setValue] = useState('');
function changeValue(value) {
setValue(value);
}
return (
<Context.Provider value={{ value: value, changeValue: changeValue }}>
<div style={{ border: '1px solid' }}>
<Child1 />
<Child2 />
</div>
</Context.Provider >
);
}
export default Parent;
2>子组件1
import React, { useContext, useState } from 'react';
import { Context } from './index';
function Child1() {
const [value, setValue] = useState('');
const { changeValue } = useContext(Context);
function inputChange(e) {
setValue(e.target.value);
}
function toChild2() {
changeValue(value);
}
return (
<div style={{ background: 'red', margin: '100px 0px' }}>
我是child1
<input type="text" onChange={inputChange} value={value} />
<button onClick={toChild2}>通知B</button>
</div>
)
}
export default Child1;
3>子组件2
import React, { useContext } from 'react';
import { Context } from './index';
function Child2() {
const { value } = useContext(Context);
return (
<div style={{ background: 'yellow' }}>
我是child2,这是child1传过来的值{value}
</div>
)
}
export default Child2;
下面在具体说一下useImperativeHandle。
useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle 应当与 forwardRef 一起使用:
//父组件
const ref = useRef();
return (
<div style={{ border: '1px solid' }}>
<Child3 ref={ref} />
<button onClick={() => ref.current && ref.current.changeValue('修改了')}>修改child3的值</button>
</div>
);
// 子组件
export default forwardRef((props, ref) => {
const [value, setValue] = useState('');
useImperativeHandle(ref, () => ({ // 暴露出去的参数以及方法
value,
changeValue
}));
function changeValue(value) {
setValue(value);
}
return (
<div ref={ref}>child3的值{value}</div>
)
});
下面说一种不太常用的方式
3.订阅-发布模式
- 新建一个event.js文件,编写一个发布订阅模式的类。
class Event {
constructor() {
this.events = this.events || new Map();
}
}
//触发
Event.prototype.emit = function(type, ...args) {
let handler = this.events.get(type);
if(handler && Array.isArray(handler)) {
for(let i=0, l=handler.length; i<l; i++) {
if(args.length > 0) {
handler[i].apply(this, args);
}else {
handler[i].call(this);
}
}
}else {
if(args.length > 0) {
handler.apply(this, args);
}else {
handler.call(this);
}
}
return;
}
// 订阅
Event.prototype.on = function(type, fn) {
const handler = this.events.get(type);
if(!handler) {
this.events.set(type, fn);
}else if(handler && typeof handler === 'function') {
this.events.set(type, [handler, fn]);
}else {
handler.push(fn);
}
}
// 移除监听
Event.prototype.remove = function(type, fn) {
const handler = this.events.get(type);
if(handler && typeof handler === 'function') {
this.events.delete(type, fn);
}else {
let position = -1;
for (let i = 0; i < handler.length; i++) {
if (handler[i] === fn) {
position = i;
return;
}
}
if(position !== -1) {
handler.splice(position, 1);
if(handler.length === 1) {
this.events.set(type, handler[0]);
}
}else {
return this;
}
}
}
export default new Event();
//子组件1
export default class Child extends Component {
setValue = () => {
event.emit('dispatch', 1); // 触发
}
render() {
return (
<div>
子组件1
<button onClick={this.setValue}>通知B</button>
</div>
)
}
}
// 子组件2
export default class Child1 extends Component {
constructor() {
super();
this.state = {
value: 0
}
}
componentDidMount() {
event.on('dispatch', this.valueChange); // 设置监听
}
valueChange = value => {
this.setState({
value
})
}
componentWillUnmount() {
event.remove('dispatch', this.valueChange);
}
render() {
return (
<div>
这是子组件的value {this.state.value}
</div>
)
}
}
4. Redux
相信熟悉React的小伙伴,对Redux肯定不是很陌生,对于一些大型项目,普遍使用Redux的还是居多的,因为这部分内容还是较多,会在之后和React-Saga进行一个整体的介绍。
三、总结
以上基本总结了我所了解的几种关于React组件间通信的几种方式,关于React通信,不管是项目中还是面试中基本都会用到和问到,希望这篇文章对大家有一定的帮助,如果你还知道其他的几种方式,欢迎在评论中指出哦。