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.子改父
子组件想通过一些操作把父组件中的信息进行修改
-
回调函数(和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();
- 【ThemeContext.Provider】祖先组件注册上下文中的内容 1.把父元素用<ThemeContext.Provider></ThemeContext.Provider>包裹起来 2.基于value向组件元素的上下文中设置内容
- 后代组件进行消费(获取到上下文中的内容)
- 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