react其组件化的思想使得组件与组件间的通信变的十分必要,下要我们来分别介绍常见的react组件之间的通信方式。
一、父子组件
react是单向数据流,父组件在展示子组件时,可能会传递一些数据给到子组件。父组件通过过属性=值的形式传递给子组件数据,子组件通过props参数获取父组件传递过来的数据。而在某些情况下我们也需要子组件向父组件传递消息,这时同样需要用到props,父组件通过props给子组件传递一个自定义的回调函数,随后在子组件中进行接收和调用,这样子组件的操作就影响了父组件中的数据 。
import React from 'react';
import ReactDOM from 'react-dom';
class SonClassComponent extends React.Component {
render() {
const { name, age } = this.props;
const { btnClick } = this.props;
return (
<div>
{/* 父传子:子组件通过props参数来获取父组件传递的数据 */}
<h4>父传子:my name is {name},{age} years old</h4>
{/* 子传父:子组件中调用这个函数 */}
<button onClick={btnClick}>+ click </button>
</div>
)
}
}
function SonFunComponent(props) {
return <h4> 父传子:my favorite book is {props.book} </h4>
}
class Father extends React.Component {
state = { name: "davina", book: '《The Dream of Red Mansion》', counter: 0 }
render() {
const { name, book, counter } = this.state;
return (<>
<h3>当前计数是:{counter}</h3>
{/* 这个地方须要注意this指向的问题 */}
<button onClick={this.addCounter}>+</button>
{/* 父传子:父组件通过属性=值传递数据 */}
{/* 子传父:还是通过props,父组件给子组件传递一个回调函数(自定义) */}
<SonClassComponent name={name} age={18} btnClick={this.addCounter} />
<SonFunComponent book={book} />
</>)
}
addCounter = () => {
this.setState({ counter: this.state.counter + 1 })
}
}
ReactDOM.render(< Father />, document.getElementById('root'))
二、属性验证(propTypes)
对于传递的数据有时我们需要进行验证,看是否符合相应的要求,一般它是用在复用性较强的组件上。我们使用propType库进行验证时,首先要导入prop-types,其次是 配置特定的propTypes属性。如果有某个属性是必须传递的,我们可以用propTypes.xxx.isRequired,或者是没有传递props但希望有一个默认值,可以使用类名.defaultProps={}或者是用到静态属性也是可以的。
//1.导入
import propTypes from 'prop-types'
import React from 'react'
import ReactDom from 'react-dom'
class Son extends React.Component {
// 类中使用static propTypes = {} 进行检证
static propTypes = {
data: propTypes.string, //data必须是一个字符串
className:propTypes.string.isRequired //设置必传属性的值
}
// 设置默认值方法一:
static defaultProps = {data: '暂无'}
render() {
let {className,data} = this.props
return <div className={className}>{data}</div>
}
}
// 设置默认值方法二
// Son.defaultProps = {data: '暂无'}
class Father extends React.Component {
state = {
data: '杨柳回塘,鸳鸯别浦,绿萍涨断莲舟路',
className: 'box'
}
render() {
let { data, className } = this.state
return (
<Son className={className} data={data}></Son>
)
}
}
ReactDom.render(<Father />, document.getElementById('root'))
三、context
对于非父子层级较深的组件进行通信时我们可以使用react提供的一个API即Context,它提供了一种在组件之间共享数据的方式,而不必通过每一层组件,可以共享一些数据,其它的组件都可以从中进行读取。有新旧两种写法。
context它有四个与之相关的api需要我们了解,即:
React.createContext:使用它可以创建一个需要共享的Context对象,如果一个组件订阅了Context,那么这个组件就会从离自身最近的那个匹配的Provider中读取到当前context的值,如果没有找到对应的Provider可以设置一个defaultValue。
Context.Provider:每个Context对象都会返回一个Provider React组件,它允许消费组件订阅context的变化。Provider接收一个value值,传递给消费组件,当Provider的value值发生变化,它内部的所有消费组件都会被重新的渲染。并且一个Provider可以和多个消费组件进行对应,多个Provider也可嵌套使用,里层数据会覆盖外层数据。
Class.contextType:是挂载在class上的contextType属性它会被重新赋值一个由React.createContext()创建的Context对象,这样我们就可以使用this.context来得到使用最近Context上的值;
Context.Consumer:使用这个api,react组件可以订阅到context的变化,这样我们可以在函数式组件中完成订阅context的任务了,强调一点的是它需要函数作为子元素,这个函数接收当前context值,返回一个react节点。
老写法:声明上下文前首先我们要用到static chiildContextTypes 声明上下文中的数据类型,其次是getChildrenContext方法将属性传递给子组件,在子组件中需要使用contextTypes声明需要用到的属性的数据类型。
//引入
import propTypes from 'prop-types'
import React from 'react'
import ReactDom from 'react-dom'
class Son extends React.Component {
//3、接收
static contextTypes ={data:propTypes.string}
render() {
//4、使用
return <div>{this.context.data}</div>
}
}
class Father extends React.Component {
//1、声明
static childContextTypes = {
data: propTypes.string,
name:propTypes.string
}
//2、传递
getChildContext() {
return {data: '杨柳回塘,鸳鸯别浦,绿萍涨断莲舟路'}
}
render() {
return <div className=''><Son></Son></div>
}
}
ReactDom.render(<Father />, document.getElementById('root'))
新写法:首先用到React.createContext创建一个全局的共享对象数据,然后使用Context对象的Provider react组件得到数据,Context.Provider它可以让每个组件订阅context的变化 ,如果一个组件订阅了Context,那么这个组件就会从离自己最近的组件匹配provider进行读取操作,这样可以使用数据了。
import React from 'react'
import ReactDom from 'react-dom'
// 1、创建
let Context = React.createContext();
class Son extends React.Component {
//3.重新赋值挂载
static contextType = Context;
render() {
//4.用this.context来使用最近Context上的那个值
return <div>{this.context.data}</div>
}
}
class Father extends React.Component {
render() {
return <div className=''><Son></Son></div>
}
}
ReactDom.render(
//2.使用Context.Provider,传递数据
<Context.Provider value={{data:'杨柳回塘,鸳鸯别浦,绿萍涨断莲舟路'}}>
<Father />
</Context.Provider>, document.getElementById('root'))
四、react中插槽实现
在react中是没有slot这个概念的,如果要实现vue中slot效果,可以直接通过props进行传输。
下面的NavChildren组件中就用到props.children属性。这个属性它在每个组件中都有,包含了组件开始和结束标记之间的内容,可以插入文本,表达式或者是节点。this.props.children的值有三种情况,如果当前组件没有子节点,那它就为undefined,如果有一个子节点,那数据类型为object,如果多个子节点,数据类型为array。当传入多个子节点,props.children就是一个数组,那就可以通过其下标访问到子节点,用以控制其出现的位置。但它对顺序有极高的要求,又因为子父组件之间的传递及组件可以接受任意的props,所以我们可以像NavBar那样直接进行使用。
// demo.js
import React, { PureComponent } from 'react';
import ReactDOM from 'react-dom';
import './style.css'
class NavChildren extends PureComponent {
render() {
// this.props.children,用来children,但是它对顺序有极高的要求
return (
<div className="nav-item nav-bar">
<div className="nav-left">{this.props.children[0]}</div>
<div className="nav-item nav-center">{this.props.children[1]}</div>
<div className="nav-item nav-right">{this.props.children[2]}</div>
</div>
)
}
}
class NavBar extends PureComponent {
render() {
return (
<div className="nav-item nav-bar">
<div className="nav-left">{this.props.slotLeft}</div>
<div className="nav-item nav-center">{this.props.slotCenter}</div>
<div className="nav-item nav-right">{this.props.slotRight}</div>
</div>
)
}
}
class App extends PureComponent {
render() {
return (
<div>
<NavChildren>
<a href='./#'>logo</a>
<div>content</div>
<span>search</span>
</NavChildren>
<NavBar slotLeft={<a href='./#'>logo</a>}
slotCenter={<div>content</div>}
slotRight={<span>search</span>} />
</div>
)
}
}
ReactDOM.render(< App />, document.getElementById('root'))
// styled.css
body {padding: 0;margin: 0;}
.nav-bar {display: flex;}
.nav-item {height: 44px;line-height: 44px;text-align: center;}
.nav-left, .nav-right {width: 15%;background-color: lightpink;}
.nav-center {flex: 1;background-color: lightblue;}
五、通信案例
// demo.js
import React, { PureComponent } from 'react';
import ReactDOM from 'react-dom';
import propTypes from 'prop-types'
import './style.css'
class HeaderComponent extends PureComponent {
constructor(props) {
super(props)
// 记录哪个被选中
this.state = { currentIndex: 0 }
}
static propTypes = {
header: propTypes.array.isRequired
}
static defaultProps = { header: [] }
render() {
//解构父组件传递过来的header
const { header } = this.props;
const { currentIndex } = this.state;
return (
<div className='total_header' >
{header.map((item, index) => {
return <div
key={item}
// 动态添加class
className={'item_header ' + (index === currentIndex ? "active" : "")}
onClick={this.currentClick.bind(this, index)}>
<span>{item}</span></div>
})}
</div>
)
}
currentClick(index) {
this.setState({ currentIndex: index })
const { currentItemClick } = this.props;
//把index传给currentItemClick
currentItemClick(index);
}
}
class App extends PureComponent {
constructor(props) {
super(props);
this.state = {
header: ['商品', '评价', '详情','推荐'],
currentHeader: '商品'
}
}
render() {
const { header, currentHeader } = this.state;
return (
<div>
{/* 父传子:用到props */}
<HeaderComponent header={header} currentItemClick={index => this.currentItemClick(index)} />
<h3>{currentHeader}</h3>
</div>
)
}
currentItemClick(index) {//要注意这里的index
this.setState({ currentHeader: this.state.header[index] });
}
}
ReactDOM.render(< App />, document.getElementById('root'))
// styled.css
.total_header{display: flex;}
.item_header{flex:1;text-align: center;margin-top: 10px;}
.item_header.active{color:red;}
.item_header.active span{border-bottom: 2px solid red;padding:5px;}