3. React的组件通讯

304 阅读5分钟

1.基于属性传递props

单项数据传递(父->子)

传递的属性不能直接修改(除非是重新调取)

import React from 'react';
import ReactDOM from 'react-dom';

class Vote2 extends React.Component {
    constructor(props) {
        super(props);
		console.log(this.props)  // {title: "今天天气不错哦~", num: 0}
	}
	
    render() {
        return <div>{this.props.title}</div>;
	}
}

ReactDOM.render(<div>
    <Vote2 title='今天天气不错哦~' num={0}></Vote2>
</div>, document.getElementById('root'));

2.子改父

子组件想通过一些操作把父组件中的信息进行修改

  1. 回调函数(和JSONP或者JSBridge机制类似):

    父组件把自身的方法通过属性传递给子组件,子组件在某些情况下,把传递进来的父组件中的方法执行,把子组件的一些信息传递给方法(底层:还是基于属性)

前两种,一般用于父子之间,当然祖先和后代之间也可以,只不过需要基于属性一层层的传递,也可以用于兄弟之间,但是需要保证,哥俩有同一个父亲(兄弟)

/* REACT中的事件是合成事件 */
/* 底层源码上是基于事件委托把所有的事件进行代理的(跨平台、跨终端) =>事件对象也是自己额外单独处理了*/
import React from 'react';
import ReactDOM from 'react-dom'

==================投票小例子=====================
// 以下投票案例既使用了通过回调函数修改父元素信息,又使用到状态改变值
class VoteMain extends React.Component {
	render() {
		let { supNum, oppNum } = this.props;
		return <main className="mainBox">
			<p>支持人数:{supNum}</p>
			<p>反对人数:{oppNum}</p>
			<p>支持率:{this.ditor()}</p>
		</main>
	}
	ditor=()=>{
		let { supNum, oppNum } = this.state,
			ratio = null,
			total = supNum + oppNum;
		ratio = total === 0 ? 0 : supNum / total * 100;
		return ratio.toFixed(2) + '%';
	}
}
class VoteFooter extends React.Component {
	render() {
		let { callback } = this.props;
		return <footer className="footerBox">
			<button onClick={ev => {
				callback('SUP', 1)
			}}>支持</button>
			{/* 用bind,默认会传一个mouseev */}
			<button onClick={callback.bind(null, 'OPP',1)}>反对</button>
		</footer>
	}
}

export default class Vote extends React.Component {
	constructor(props) {
		super(props);
		this.state = {
			supNum: 0,
			oppNum: 0
		}
	}
	render() {
		let { supNum, oppNum } = this.state;
		return <div className="voteBox">
			<header className="headerBox">
				<h3>{this.props.title}</h3>
				<span>N:{supNum + oppNum}</span>
			</header>
			{/* 当父组件渲染的时候,也会重新渲染子组件,我们可以把支持反对的状态信息传递给子组件 */}
			<VoteMain supNum={supNum} oppNum = {oppNum}></VoteMain>
			{/* 子组件想要调取父组件的方法,以此实现子改父 */}
			<VoteFooter callback={this.handle}></VoteFooter>
		</div>;
	}
	// 修改状态的信息(状态信息的修改会触发render重新渲染)
	handle = (type, num = 1) => {
		let { supNum, oppNum } = this.state;
		this.setState({
			supNum: type === 'SUP' ? supNum + num : supNum,
			oppNum: type === 'OPP' ? oppNum + num : oppNum
		})
	}
}

ReactDOM.render(<div>
	<Vote2 title='今天天气好的很ne ~' supNum={0} oppNum={0}>
	</Vote2>
</div>, document.getElementById('root'));

2.基于发布订阅实现组件信息通信

自己实现发布订阅的源码(自定义发布订阅的源码)

/* EventEmit: 自定义发布订阅改变父子,兄弟之间的信息通信
* 1.$on:把自定义[eventName]的方法[func]放在一个事件池的数组中
		1. 判断是否私有,非私有的,创建一个初始值[]
		2. 订阅方法去重
* 2.$emit:调用时,循环存储在事件池的方法执行
*/
class EventEmit {
	//=>事件池 {事件名:[订阅方法,...]}
	pond = {};
	$on(eventName, func) {
		// 如果没有此事件,我们初始化创建一个值
		if (!this.pond.hasOwnProperty(eventName)) {
			this.pond[eventName] = [];
		}
		// 订阅方法的去重
		if (this.pond[eventName].some(item => item === func)) return;
		this.pond[eventName].push(func);
	}
	$emit(eventName, ...args) {
		let arr = this.pond[eventName] || [];
		arr.forEach(item => {
			item.call(null, ...args);
		});
	}
}
export default new EventEmit();

基于发布订阅实现子改父,兄弟信息通信

import React from 'react';
import PropTypes from 'prop-types';
import EM from './EventEmit';
import './Vote.less';

class VoteMain extends React.Component {
	state = {
		supNum: 0,
		oppNum: 0
	};
	handle = type => {
		let { supNum, oppNum } = this.state;
		type === 'SUP' ? this.setState({ supNum: supNum + 1 }) : this.setState({ oppNum: oppNum + 1 });
	};
	render() {
		let { supNum, oppNum } = this.state;
		return <main className="mainBox">
			<p>支持人数:{supNum}</p>
			<p>反对人数:{oppNum}</p>
		</main>;
	}
	componentDidMount() {
		// 订阅自定义事件方法
		EM.$on('mainHandle', this.handle);
	}
}

class VoteFooter extends React.Component {
	render() {
		return <footer className="footerBox">
			<button onClick={ev => {
				// 通知订阅的方法执行
				EM.$emit('mainHandle', 'SUP');
				EM.$emit('indexHandle');
			}}>支持</button>
			<button onClick={ev => {
				EM.$emit('mainHandle', 'OPP');
				EM.$emit('indexHandle');
			}}>反对</button>
		</footer>;
	}
}

export default class Vote extends React.Component {
	state = { total: 0 };
	render() {
		return <div className="voteBox">
			<header className="headerBox">
				<h3>{this.props.title}</h3>
				<span>N:{this.state.total}</span>
			</header>
			<VoteMain></VoteMain>
			<VoteFooter></VoteFooter>
		</div>;
	}
	componentDidMount() {
		EM.$on('indexHandle', () => {
			this.setState({
				total: this.state.total + 1
			});
		});
	}
}

3.执行上下文进行信息传递

1.static defaultProps(传统)

把后代中需要的都放到祖先的上下文中,后代需要直接获取使用即可

/**
 * 祖先元素
 * 	1.在祖先元素上使用static childContextTypes给需要使用的数据做格式验证
 * 	2.把挂载到祖先上下文中的数据放置到祖先的状态上 this.state={}
 * 	3.getChildContext(){return{}} 把状态数据放在里面(每当祖先组件中的状态改变,重新渲染的时候,此钩子函数也会重新被执行)
 * 子元素
 * 	1.通过static contextTypes = {} 设置所用数据的格式
 * 	2.获取的上下文信息挂载到实例的this.context中了(获取的上下文信息是可以修改的,但是并没有影响到祖先)
 */

import React from 'react';
import PropTypes from 'prop-types';

class Vote1 extends React.Component {
	static contextTypes ={
		num1: PropTypes.number,
		num: PropTypes.number,
	}
	render() {
		return <div>
			<p>我是:{this.context.num}</p>
			<p>你是:{this.context.num1}</p>
		</div>
	}
}
class Vote2 extends React.Component {
	static contextTypes = {
		handle: PropTypes.func
	};
	render() {
		return <div>
			<button onClick={ev=>{
				this.context.handle('top')
			}}>点我加(+N)</button>
			<button  onClick={ev=>{
				this.context.handle()
			}}>点我加(+M)</button>
		</div>
	}
}

class Vote extends React.Component {
	// 设置默认属性及规则
	static defaultProps = {
		title: '北京天气真好~',
		handle: this.handle
	};
	static propTypes = {
		title: PropTypes.string
	};

	static childContextTypes = {
		num1: PropTypes.number,
		num: PropTypes.number,
		handle: PropTypes.func
	}
	getChildContext() {
		return {
			num :this.state.num,
			num1 :this.state.num1,
			handle:this.handle
		}
	}
	state = {
		num1: 0,
		num: 0
	}
	render() {
		let { num, num1 } = this.state;
		return <div>
			<h2>{this.props.title}
				<span>{num + num1}</span>
			</h2>
			<Vote1></Vote1>
			<Vote2></Vote2>
		</div>
	}
	handle = type => {
		let { num, num1 } = this.state;
		type === 'top' ? this.setState({ num: num + 1 }) : this.setState({ num1: num1 + 1 });
	}
}


ReactDOM.render(<div>
	<Vote></Vote>
</div>, document.getElementById('root'))

2. ThemeContext.Provider

  • 1.创建一个上下文对象 let ThemeContext = React.createContext();
    1. 【ThemeContext.Provider】祖先组件注册上下文中的内容 1.把父元素用<ThemeContext.Provider></ThemeContext.Provider>包裹起来 2.基于value向组件元素的上下文中设置内容
    1. 后代组件进行消费(获取到上下文中的内容)
    • 1.在需要使用的子元素中设置static contextType = ThemeContext(提供的内容放到this.context中了) 直接{this.context.xxx}
    • 2.需要把子组件用ThemeContext.Consumer包裹起来,子元素内容用函数包裹起来,context就是祖先中注册的上下文信息 context===value
import React from 'react';

let ThemeContext = React.createContext();
class CountShow extends React.Component {
    // 2. 后代消费(第一种)
    static contextType = ThemeContext;
    render() {
        return <div>{this.context.num}</div>
    }
}

class CountButton extends React.Component {
    static contextType = ThemeContext;
    render() {
        return <ThemeContext.Consumer>
            {/* 第二种消费方式:ThemeContext.Consumer 需要把子元素内容用函数包裹起来
            context就是祖先中注册的上下文信息 context===value*/}
            {value => {
                return <button onClick={
                    ev => {
                        value.handle()
                    }
                }>累加</button>
            }}
        </ThemeContext.Consumer>
    }
}
class Count extends React.Component {
    state = {
        num: 0,
        handle: this.handle
    }
    handle = () => {
        this.setState({
            num: this.state.num + 1
        })
    }
    render() {
        // 1.基于value 向祖先元素的上下文设置内容
        return <ThemeContext.Provider value={{ num: this.state.num, handle: this.handle }}>
            <h3>计数器</h3>
            <CountShow></CountShow>
            <CountButton></CountButton>
        </ThemeContext.Provider>
    }
}

export default Count