组件通信
1、父组件向子组件传值

方式一: 父组件将自己的状态传递给子组件,子组件当做属性来接收,当父组件更改自己状态的时候,子组件接收到的属性就会发生改变
方式二: 在子组件中定义一个方法,接受一个形参, 在父组件中调用子组件的该方法,将数据传递给子组件,通过ref属性获取子组件的实例对象,进而调用子组件上的方法
注意: 使用ref 属性只能使用在类组件上,函数组件上不能使用ref属性,报错
2、子组件向父组件传值
在父组件中定义一个方法,该方法接受一个形参,父组件将该方法传递给子组件,子组件中调用父组件传递的方法,并将数据传给给方法.
3、跨组件通信(数据共享)
在react没有类似vue中的事件总线来解决这个问题。在实际的项目中,当需要组件间跨级访问信息时,如果还使用组件层层传递props,此时代码显得不那么优雅,甚至有些冗余。在react中,我们还可以使用context来实现跨级父子组件间的通信。
函数组件的 使用context 实现的跨组件通信相对简单些,后面讲到函数组件说.
1 2 3 4 5 6
| import React, { Component, createContext } from "react"
const { Provider, Consumer } = createContext()
|
提示:在React的context中,数据被看成了商品,发布数据的组件会用provider身份,接收数据的组件使用consumer身份。

Context需要在父级或祖先级组件中使用,同时也需要在获取数据的子组件中使用, 所以需要将该对象定义成全局对象(即定义一个单独的js文件中), 然后在父级/祖先级 组件中引入,同时也在获取数据的子组件中引入.
1 2 3 4
| // 定义全局context // 由于这个操作后期可能被复用,建议独立文件去创建。此处以`src/Context/index.js`为例 import { createContext } from "react" export default createContext() // createContext函数的的参数就是提供的默认值,如果后续没有给provider赋值value属性,那么 createContext({a:1,b:2,c:3});{a:1,b:2,c:3}就是value的默认值
|
在App.jsx组件中发布消息,这样所有的组件都可以消费它的消息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import React, { Component } from "react"; import Cmp1 from "./Components/Cmp1"; import Cmp2 from "./Components/Cmp2"; // 导入context对象 import ContextObj from "./Context/index"; let { Provider } = ContextObj;
class App extends Component { state = { count: 12345, };
render() { return ( <div> {/* 将需要接受数据的子组件放到Provider组件内,否则子组件接受不到数据 value 属性指定要传递的数据*/} <Provider value={this.state.count}> <Cmp6></Cmp6> <Cmp7></Cmp7> </Provider> </div> ); } }
export default App;
|
在子组件中通过Api完成消费动作,从而实现消息通信。消费的方式有2种:
方式1:通过组件消费
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import React, { Component } from "react";
import ContextObj from "../Context/index"; let { Consumer } = ContextObj;
class Cmp1 extends Component { render() { return ( <div> <Consumer> {/* 注意:Consumer组件中只能是函数形式, */} {(value) => { return <div>获取到的值是:{value}</div>; }} </Consumer> </div> ); } }
export default Cmp1;
|
方式2:通过绑定静态成员属性来消费
1 2 3 4 5 6 7 8 9 10 11 12
| import React, { Component } from "react"; import ContextObj from "../Context/index";
class Cmp2 extends Component { static contextType = ContextObj; //contextType 是内置固定属性,不能修改 render() { // this.context 就是共享的数据, 因为context在赋值给静态成员属性contextType时,做了一个属性映 射,将context内的context属性映射到组件实例上,所以如下就可以通过this.context获取对应的数据 return <div>{this.context}</div>; } }
export default Cmp2;
|
4、兄弟组件间通信
这里我们采用自定义事件的方式来实现非嵌套组件间的通信。
我们需要使用一个 events 包:第三方的包不属于react全家桶的包
1
| npm install events --save
|
新建一个 ev.js,引入 events 包,并向外提供一个事件对象,供通信时使用:
1 2
| import { EventEmitter } from "events"; export default new EventEmitter();
|
父组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import React, { Component } from 'react'; import Child1 from "./Foo"; import Child2 from "./Boo";
export default class App extends Component{ render(){ return( <div> <Child1 /> <Child2 /> </div> ); } }
|
Child1组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import React,{ Component } from "react"; import Bus from "./ev" export default class Boo extends Component{ emitFn = () => { // 触发 (自定义事件,传递的参数) 和vue一样 Bus.emit("transfer",this.state.msg) } state={ msg:'舒克' } render() return( <div> 组件1 <button onClick = {()=> this.emitFn() }>点击我</button> </div> ); } }
|
Child2组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import React,{ Component } from "react"; import Bus from "./ev" export default class Foo extends Component{ getData = (msg) => { console.log(msg) // 这就是传递过来的参数 } componentDidMount(){ // 组价2 中接收组件1传过来的自定义事件 Bus.on("transfer",this.getData); } render(){ return( <div> 组件2 </div> ); } }
|
自定义事件是典型的发布/订阅模式,通过向事件对象上添加监听器和触发事件来实现组件间通信。
高阶组件
Higher - Order Components:在原有组件基础之上加工后新生成得到的新组件。【高阶组件】
1
| const NewComponent = HOC(YourComponent)
|
通俗的来讲,高阶组件
就相当于手机壳,通过包装组件,增强组件功能

自定义高阶组件
HOC实现步骤:
创建一个函数
指定函数参数,参数应该以大写字母开头
在函数内部创建一个类组件,提供复用的状态(如有)及逻辑代码,并返回
在该组件中,渲染参数组件,同时将状态通过prop传递给参数组件(可选,如有)
调用该高阶组件方法,传入要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面
比如,我们想要我们的组件通过自动注入一个版权信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| // 如下结构和函数的组件结构很相似,所以可以使用rfc,创建一个函数组件然后在此基础上修改 import React, { Component, Fragment } from "react"; const withCopyright = (Cmp) => { return class Hoc extends Component { render() { return ( <Fragment> <Cmp></Cmp> <div>© 2020 千峰教育</div> </Fragment> ); } }; };
export default withCopyright;
// Fragment是一个伪标签,渲染的时候是不会显示在页面中的,因此也不会影响视图显示 // 此处注意:如果该Cmp 组件 在使用withCopyright 方法之前,是作为它的父组件下的子组件,并且接受了来组父组件和参数props,那么通过 withCopyright 方法,导致原先的父子组件变成了 爷孙组件,且孙组件无法接受爷组件的数据,此时了使用中间组件传递一下,而withCopyright 返回的组件就是中间组件 写法为: <Fragment> <Cmp {...this.props}></Cmp> <div>© 2020 千峰教育</div> </Fragment>
|
使用方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import React, { Component } from "react"; // 引入HOC函数 import Hoc from './Hoc/Hoc_copyright';
class App extends Component { render() { return ( <div> <h1>网站首页</h1> </div> ); } }
export default Hoc(App);
|
这样只要我们有需要用到版权信息的组件,都可以直接使用withCopyright这个高阶组件包裹即可。
常见应用场景
1.需要代码重用时, react如果有多个组件都用到了同一段逻辑
, 这时,就可以把共同的逻辑部分提取出来,利用高阶组件的形式将这段逻辑整合到每一个组件中, 从而减少代码的逻辑重复
2.需要组件增强优化时
, 比如我们在项目中使用的组件有些不是自己写的, 而是从网上撸下来的,但是第三方
写的组件可能比较复杂, 有时不能完全满足需求, 但第三方组件不易修改, 此时也可以用高阶组件,在不修改原始组件的前提下, 对组件添加满足实际开发需求的功能
3.可以对原有组件
中的state, props和逻辑执行增删改操作, 一般用于代码重用
和组件增强优化
内置高阶组件memo
React.memo 是一个高阶组件,接受一个组件作为参数返回一个新的组件。新的组件仅检查 props 变更,会将当前的 props 和 上一次的 props 进行浅层比较,相同则阻止渲染。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| import React, { Component } from 'react' import TestSun from './TestSun'
export default class Test extends Component { state={ n:1 } inc=()=>{ this.setState({ n:this.state.n+1 }) } render() { return ( <div> {this.state.n} <button onClick={this.inc}>+</button> <TestSun></TestSun> </div> ) } } 可以看到子组件TestSun使用高阶组件memo后,不会是父组件渲染,它也会跟着渲染了 import React, { Component,memo } from 'react'
export default memo(class TestSun extends Component { render() { console.log("TestSun render") return ( <div> testSun </div> ) } })
|