react总结之组件通信,props,context

  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;}