react 组件通讯

server/2024/11/13 15:54:39/

组件通讯

  • 组件是独立且封闭的单元,默认情况下,只能使用组件自己的数据。
  • 在组件化过程中,我们将一个完整的功能拆分成多个组件,以更好的完成整个应用的功能。
  • 而在这个过程中,多个组件之间不可避免的要共享某些数据。
  • 为了实现这些功能,就需要打破组件的独立封闭性,让其与外界沟通。这个过程就是组件通讯
  • 在react中,数据来源主要有两个,一个是state内部数据,另一个是props外部数据,这两个数据发生改变后都会引起组件的更新渲染。
  • 父组件在展示子组件,可能会传递一些数据给子组件:
  • 父组件通过 属性=值 的形式来传递给子组件数据;
  • 子组件通过 props 参数获取父组件传递过来的数据

 

props

  • 组件是封闭的,要接收外部数据应该通过props来实现
  • props的作用:接收传递给组件的数据
  • 传递数据:给组件标签添加属性
  • 接收数据:函数组件通过参数props接收数据,类组件通过this.props接收数据

函数组件通讯

基本数据类型传递

子组件

import React, { memo } from 'react'
// import PropTypes from 'prop-types'function demoFuncClassComponent(props) {return (<div><h4>函数组件</h4><p>{props.title}</p></div>)
}// 定义props的类型
// demoFuncClassComponent.propTypes = {}export default memo(demoFuncClassComponent)

父组件

import './App.css'import DemoFuncClassComponent from './components/demoFuncClassComponent'function App() {return (<div className="App"><DemoFuncClassComponent title="我是父组件传递给子组件的数据" /></div>)
}export default App

引用数据类型传递

如果子组件接收的数据是一个一个的字段,在对象中有很多数据需要传入,那么你可以使用es6中的扩展语法将对象的数据全部扩展进去

子组件Props.jsx

import React, { memo } from 'react'
// import PropTypes from 'prop-types'function demoFuncClassComponent(props) {return (<div><h4>函数组件</h4><p>{props.title}</p><p>{props.name}</p><p>{props.age}</p><p>{props.address}</p></div>)
}// 定义props的类型
// demoFuncClassComponent.propTypes = {}export default memo(demoFuncClassComponent)

父组件App.jsx

import './App.css'import DemoFuncClassComponent from './components/demoFuncClassComponent'let user = {name: '张三',age: 20,address: '渝北区',
}function App() {return (<div className="App"><DemoFuncClassComponent title="我是父组件传递给子组件的数据" {...user} /></div>)
}export default App

如果子组件接收的是整个对象,那么你可以直接将对象传入

子组件Props.jsx

import React from 'react'export default function Props(props) {console.log(props);return (<div>标题:{props.title}<br />姓名:{props.user.name}<br />年龄:{props.user.age}<br />地址:{props.user.address}</div>)
}

父组件App.jsx

import Props from "./components/Props";let user = {name: '张三',age: 20,address: '渝北区'
}function App() {return (<div><Props title='标题' user={user} /></div>);
}export default App;

类组件通讯

类组件的外部数据通过this.props来访问

import React, { Component } from 'react'export default class PropsClass extends Component {constructor(props) {super(props);}render() {return (<div>标题:{this.props.title}</div>)}
}

父组件:App.jsx传入数据

import Props from "./components/Props";function App() {return (<div><Props title='标题' /></div>);
}export default App;

props的特点

  • 可以给组件传递任意类型的数据
  • props是只读的,不允许修改props的数据
  • 注意:在类组件中使用的时候,需要把props传递给super(),否则构造函数无法获取到props
class Hello extends React.Component {constructor(props) {// 推荐将props传递给父类构造函数super(props)}render() {return <div>接收到的数据:{this.props.age}</div>}
}

组件通讯方式

  • 父传子
  • 子传父
  • 非父子

父传子

在vue中,父传子通过props传递,子传父通过$emit触发自定义事件进行传参。

在react中,父子组件通信,父传子通过props传递;子传父通过调用父组件传递的函数通知父组件进行修改数据:

  1. 父组件提供要传递的state数据
  1. 给子组件标签添加属性,值为 state 中的数据
  1. 子组件中通过 props 接收父组件中传递的数据

App.js

import './App.css';
import React from 'react';
import Parent from './components/Parent'function App() {return (<div><Parent/></div>);
}export default App;

父组件:提供数据并且传递给子组件--components/Parent.jsx

import React, { Component } from 'react'
import Child from './Child'
export default class Parent extends Component {state = {title: '星期一'}render() {return (<div><Child title={this.state.title}></Child></div>)}
}

子组件:接收数据

import React, { Component } from 'react'export default class Child extends Component {render() {return (<div><p>接收父组件传递过来的参数--{this.props.title}</p></div>)}
}

子传父

思路:利用回调函数,父组件提供回调,子组件调用,将要传递的数据作为回调函数的参数。

  1. 父组件提供一个回调函数(用于接收数据)
  1. 并将该函数作为属性值,传递给子组件
  1. 子组件通过 props 调用回调函数
  1. 将子组件的数据作为参数传递给回调函数

App.js

import './App.css';
import React from 'react';
import Parent from './components/Parent'function App() {return (<div><Parent/></div>);
}export default App;

父组件提供函数--components/Parent.jsx

类似于js里的将函数当做参数传递给子组件

import React, { Component } from 'react';
import ClassSub from './classSub';
class classHello extends Component {constructor(){super()this.state = {counter: 0}}// 父组件提供一个回调函数(用于接收数据)changeCounter = (count) => {this.setState({counter: this.state.counter + count})}render() {const {counter} = this.statereturn (<div><p>当前值:{counter}</p><ClassSub changeCounter = {(count) => this.changeCounter(count)} /></div>);}
}export default classHello;

子组件接收函数并且调用--components/Child.jsx

import React, { Component } from 'react'export default class classSub extends Component {handlerClick = (count) => {this.props.changeCounter(count)}render() {return (<div><button onClick={() => this.handlerClick(1)}>子传父+1</button><button onClick={() => this.handlerClick(5)}>子传父+5</button><button onClick={() => this.handlerClick(10)}>子传父+10</button></div>)}
}

 

父子通讯示例

tab栏

利用父传子,子传父

父组件:

import React, { Component } from 'react'
import TabControl from './TabControl'
export default class classHello extends Component {constructor(){super()this.state = {titles: ["流行","新款", "精选"],tabIndex: 0}}tabClick = (index) => {this.setState({tabIndex: index})}render() {const {titles, tabIndex} = this.statereturn (<div><TabControl titles={titles} tabClick={(index) => this.tabClick(index)} /><div>{titles[tabIndex]}</div></div>)}
}

子组件:

import React, { Component } from 'react';
import './TabControl.css'
class TabControl extends Component {constructor(){super()this.state = {currentIndex: 0,}}itemClick = (index) => {this.setState({currentIndex: index})this.props.tabClick(index)}render() {const {titles} = this.propsconst {currentIndex} = this.statereturn (<div className='tab-control'>{titles.map((item, index) => {return (<div className={`item ${index === currentIndex ? 'active' : ''}`} key={item}onClick={() => this.itemClick(index)}><span className='text'>{item}</span></div>)})}</div>);}
}export default TabControl;

样式:

.tab-control {display: flex;align-items: center;text-align: center;height: 40px;
}.tab-control .item {flex: 1;
}.tab-control .item.active {color: red;
}.tab-control .item.active .text {border-bottom: 3px solid red;padding: 3px;
}

props 和 callback 一起使用
// 子组件
function Son(props) {const { fatherSay, sayFather } = props;return (<div className="son">我是子组件<div>父组件对我说:{ fatherSay }</div><input placeholder="我对父组件说" onChange={ e =>  sayFather(e.target.value)}></input></div>)
}// 父组件
function Father() {const [ childSay, setChildSay ] = useState("");const [ fatherSay, setFatherSay ] = useState("");return(<div className="box father">我是父组件<div>子组件对我说:{ childSay }</div><input placeholder="我对子组件说" onChange={ e =>  setFatherSay(e.target.value)}></input><Son fatherSay={fatherSay} sayFather={setChildSay}></Son></div>)
}

 

组件插槽实现

在开发中,我们抽取了一个组件,但是为了让这个组件具备更强的通用性,我们不能将组件中的内容限制为固定的div、span等等这些元素

我们应该让使用者可以决定某一块区域到底存放什么内容。

这种需求在Vue当中有一个固定的做法是通过slot来完成的,React呢?

React对于这种需要插槽的情况非常灵活,有两种方案可以实现:

  1. 组件的children子元素
  1. props属性传递React元素

children实现插槽
  • 每个组件都可以获取到 props.children:它包含组件的开始标签和结束标签之间的内容
  • 通过children实现的方案虽然可行,但是有一个弊端:通过索引值获取传入的元素很容易出错,不能精准的获取传入的元素

父组件:

import React, { Component } from 'react'
import NavBar from './NavBar'
export default class classHello extends Component {render() {return (<div><NavBar><button>按钮</button><h2>标题</h2><i>斜体文字</i></NavBar></div>)}
}

注意:如果在父组件里只传递一个元素,那么在子组件里这一个就是children,并不是数组了

例如:

父:
<NavBar><i>斜体文字</i>
</NavBar>子:
<div className="left">{children}</div>

子组件:

import React, { Component } from 'react';
import './navBar.css'
class NavBar extends Component {render() {// 注意:放置多个的时候拿到的children就是一个数组// 放置一个的时候children就是那一个元素/内容const {children} = this.propsreturn (<div className='nav-bar'><div className="left">{children[0]}</div><div className="center">{children[1]}</div><div className="right">{children[2]}</div></div>);}
}export default NavBar;

样式:

*{padding: 0;margin: 0;
}
.nav-bar {display: flex;height: 40px;text-align: center;line-height: 40px;
}.left, .right {width: 80px;background-color: green;
}.center {flex: 1;background-color: aqua;
}

props实现插槽
  • 通过具体的属性名,可以让我们在传入和获取时更加的精准

父组件:

import React, { Component } from 'react'
import NavBar from './NavBar'
export default class classHello extends Component {render() {const lefeSlot = <button>按钮</button>const centerSlot = <h2>标题</h2>const rightSlot = <i>斜体文字</i>return (<div><NavBar lefeSlot={lefeSlot} centerSlot={centerSlot} rightSlot={rightSlot} /></div>)}
}

子组件:

import React, { Component } from 'react';
import './navBar.css'
class NavBar extends Component {render() {const {lefeSlot, centerSlot, rightSlot} = this.propsreturn (<div className='nav-bar'><div className="left">{lefeSlot}</div><div className="center">{centerSlot}</div><div className="right">{rightSlot}</div></div>);}
}export default NavBar;

样式:

*{padding: 0;margin: 0;
}
.nav-bar {display: flex;height: 40px;text-align: center;line-height: 40px;
}.left, .right {width: 80px;background-color: green;
}.center {flex: 1;background-color: aqua;
}

 

作用域插槽
  • 根据回调函数可以返回不同的内容来实现
  • 组件的标签由父组件决定,但是渲染的内容由子组件决定

父组件:

import React, { Component } from 'react'
import TabControl from './TabControl'
export default class classHello extends Component {constructor(){super()this.state = {titles: ["流行","新款", "精选"],tabIndex: 0}}tabClick = (index) => {this.setState({tabIndex: index})}getTabItem = (item) => {if(item === '流行'){return <span>{item}</span>}else if(item === '新款'){return <button>{item}</button>} else {return <i>{item}</i>}}render() {const {titles, tabIndex} = this.statereturn (<div><TabControl titles={titles} tabClick={(index) => this.tabClick(index)}/**itemType={(item) => <span>{item}</span>} */itemType={item => this.getTabItem(item)}/><div>{titles[tabIndex]}</div></div>)}
}

子组件:

import React, { Component } from 'react';
import './TabControl.css'
class TabControl extends Component {constructor(){super()this.state = {currentIndex: 0,}}itemClick = (index) => {this.setState({currentIndex: index})this.props.tabClick(index)}render() {const {titles, itemType} = this.propsconst {currentIndex} = this.statereturn (<div className='tab-control'>{titles.map((item, index) => {return (<div className={`item ${index === currentIndex ? 'active' : ''}`} key={item}onClick={() => this.itemClick(index)}>{/* <span className='text'>{item}</span>*/}{itemType(item)}</div>)})}</div>);}
}export default TabControl;

样式:

.tab-control {display: flex;align-items: center;text-align: center;height: 40px;
}.tab-control .item {flex: 1;
}.tab-control .item.active {color: red;
}.tab-control .item.active .text {border-bottom: 3px solid red;padding: 3px;
}

 

兄弟组件

  • 将共享状态提升到最近的公共父组件中,由公共父组件管理这个状态
  • 思想:状态提升
  • 公共父组件职责:
    • 提供共享状态
    • 提供操作共享状态的方法
  • 要通讯的子组件只需通过 props 接收状态或操作状态的方法

状态提升前

状态提升之后

 

利用状态提升

其实就是利用子传父、父传子实现

App.js

import React, { Component } from 'react'
import Jack from './components/Jack'
import Rose from './components/Rose'export default class App extends Component {// 1. 状态提升到父组件state = {info: ''}changeInfo = (info) => {this.setState({info})}render() {return (<div>{/*4. 更改属性值*/}<Jack say={this.changeInfo} />{/* 2. 将状态给子组件进行显示 */}<Rose info={this.state.info} /></div>)}
}

components/Jack.jsx

import React, { Component } from 'react'export default class Jack extends Component {// 3. 子传父say = () => {this.props.say('you jump i look')}render() {return (<div><h3>Jack:</h3><button onClick={this.say}>say</button></div>)}
}

components/Rose.jsx

import React, { Component } from 'react'export default class Rose extends Component {render() {return (<div><h3>Rose</h3><p>Jack say:{this.props.info}</p></div>)}
}

 

利用 ref 实现

在 React 的开发模式中,通常情况下不需要,也不建议直接操作DOM,但是某些特殊的情况需要获取到DOM进行某些操作:

  • 管理焦点,文本选择或媒体播放
  • 触发强制动画
  • 集成第三方DOM库

App.js

import './App.css';
import React from 'react';
import Parent from './components/Parent'function App() {return (<div><Parent/></div>);
}export default App;

components/Parent.jsx

通过父组件作为中转站去获取子组件实例操作子组件的方法或数据

import React, { Component } from "react";
import Child from "./Child";
import Child2 from "./Child2";
export default class Parent extends Component {constructor(props) {super(props);this.childRef = React.createRef();}// 在父组件里通过 ref 去调用子组件里的那个更改自己属性值的方法,并将参数传递过去changeTitle = () => {this.childRef.current.changeTitle("马上国庆节");};render() {return (<div><Childref={this.childRef}/><Child2 onChangeTitle={this.changeTitle} /></div>);}
}

components/Child.jsx

定义内部state的title

import React, { Component } from 'react'export default class Child extends Component {state = {title: '今天星期一'
}// 在子组件内自定义一个方法去更改自己的属性值
changeTitle(title) {this.setState({title})
}render() {return (<div>title: {this.state.title}</div>);
}
}

components/Child2.jsx

import React, { Component } from 'react'export default class Child2 extends Component {updateTitle = () => {this.props.onChangeTitle();
}render() {return (<div><button onClick={this.updateTitle}>修改title</button></div>)}
}

总结:

这种方式稍微感觉有点麻烦,如果组件嵌套过深,那么通信显得非常繁琐,会一层一层往上传递,然后又一层一层往下传递。

基于事件总线

事件总线的方式,其实就是一种设计模式,被称为订阅和发布模式,设计模式一前辈们总结出来的思想,设计模式有23三种,比如还有单例模式,策略模式,适配模式等等

使用 events 插件

当然利用 eventBus 也可以实现组件通信,但是在 React 中并不提倡用这种方式,我还是更提倡用 props 方式通信。如果说非要用 eventBus,我觉得它更适合用 React 做基础构建的小程序,比如 Taro。

在react没有内置事件总线的方式,我们需要下载第三方插件events

  • 触发事件:emit('事件名称', 参数)
  • 监听事件:addListener('事件名称', 监听函数)
  • 移除事件:removeLIstener('事件名称',  监听函数)

  1. 安装插件
yarn add events
# or
npm i events

  1. 创建eventBus文件
    src/utils/eventBus/eventBus.js
import { EventEmitter } from 'events';export default new EventEmitter(); // 生成一个eventBus实例

  1. App.js
import './App.css';
import React from 'react';
import Parent from './components/Parent'function App() {return (<div><Parent/></div>);
}export default App;


components/Parent.jsx

import React, { Component } from "react";
import Child from "./Child";
import Child2 from "./Child2";
export default class Parent extends Component {render() {return (<div><Child/><Child2 /></div>);}
}

  1. 在组件中订阅和发布事件
    订阅事件:Child.jsx
    components/Child.jsx
import React, { Component } from "react";
import eventBus from "../utils/eventBus/eventBus.js";export default class Child extends Component {state = {age: 20,};//子组件自定义更改年龄方法changeAge = (age) => {this.setState({age,});};componentDidMount() {// 订阅事件eventBus.addListener("updateAge", this.changeAge);}componentWillUnmount() {// 移除事件eventBus.removeListener('updateAge', this.changeAge)}render() {return (<div>age: {this.state.age}</div>)}
}


发布事件:Child2.jsx
components/Child2.jsx

import React, { Component } from 'react'
import eventBus from "../utils/eventBus/eventBus.js";export default class Child2 extends Component {changeAge = () => {eventBus.emit('updateAge', 18)
}render() {return (<div><button onClick={this.changeAge}>修改age</button></div>)}
}

使用 hy-event-store 插件

安装:

yarn add hy-event-store
# or
npm i hy-event-store

App.jsx

import React, { Component } from 'react'
import Home from './Home'
import eventBus from './utils/event-bus'export class App extends Component {constructor() {super()this.state = {name: "",age: 0,height: 0}}componentDidMount() {// eventBus.on("bannerPrev", (name, age, height) => {//   console.log("app中监听到bannerPrev", name, age, height)//   this.setState({ name, age, height })// })eventBus.on("bannerPrev", this.bannerPrevClick, this)eventBus.on("bannerNext", this.bannerNextClick, this)}bannerPrevClick(name, age, height) {console.log("app中监听到bannerPrev", name, age, height)this.setState({ name, age, height })}bannerNextClick(info) {console.log("app中监听到bannerNext", info)}componentWillUnmount() {eventBus.off("bannerPrev", this.bannerPrevClick)}render() {const { name, age, height } = this.statereturn (<div><h2>App Component: {name}-{age}-{height}</h2><Home/></div>)}
}export default App

Home.jsx

import React, { Component } from 'react'
import HomeBanner from './HomeBanner'export class Home extends Component {render() {return (<div><h2>Home Component</h2><HomeBanner/></div>)}
}export default Home

HomeBanner.jsx

import React, { Component } from 'react'
import eventBus from "./utils/event-bus"export class HomeBanner extends Component {prevClick() {console.log("上一个")eventBus.emit("bannerPrev", "why", 18, 1.88)}nextClick() {console.log("下一个")eventBus.emit("bannerNext", {nickname: "kobe", level: 99})}render() {return (<div><h2>HomeBanner</h2><button onClick={e => this.prevClick()}>上一个</button><button onClick={e => this.nextClick()}>下一个</button></div>)}
}export default HomeBanner

utils/event-bus.js

import { HYEventBus } from "hy-event-store"const eventBus = new HYEventBus()export default eventBus

总结:

eventBus订阅发布的方式给组件之间直接通信仍然存在一些缺陷,订阅和发布事件绑定太多的话,会非常的分散,对于以后的维护来说不太好维护,你就不知道哪里在监听,哪里在触发,哪里要移除事件,不太好管理

我们可以使用其他的方式,例如redux,进行数据的管理

跨级通讯-context

上下文

在 vue 里是通过 provide inject ,但是在 react 中可以通过 context 实现类似的

基本概念

思考:App 组件要传递数据给 Child 组件,该如何处理?

处理方式:使用 props 一层层组件往下传递(繁琐)

更好的用法:使用 Context

作用:跨组件传递数据(比如:主题、语言等)

Context相当于一个公共的存储空间:

  • 我们可以将多个组件中需要访问的数据统一存储到一个Context中
  • 这样我们无需通过props逐层传递,即可使组件中访问到这些数据

相关API

React.createContext:

  • 创建一个需要共享的Context对象
  • 如果一个组件订阅了Context,那么这个组件会从离自身最近的那个匹配的 Provider 中读取到当前的context值
  • defaultValue是组件在顶层查找过程中没有找到对应的Provider,那么就使用默认值
const MyContext = React.createContext(defaultValue)

Context.Provider:

  • 每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化
  • Provider 接收一个 value 属性,传递给消费组件
  • 一个 Provider 可以和多个消费组件有对应关系
  • 多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据
  • 当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染
<MyContext.provider value={/*某个值*/}></MyContext>

Class.contextType:

  • 挂载在 class 上的 contextType 属性会被重赋值为一个由 React.createContext() 创建的 Context 对象
  • 这能让你使用 this.context 来消费最近 Context 上的那个值
  • 你可以在任何生命周期中访问到它,包括 render 函数中
MyClass.contextType = MyContext;

Context.Consumer:

  • 这里,React 组件也可以订阅到 context 变更。这能让你在 函数式组件 中完成订阅 context
  • 这里需要 函数作为子元素(function as child)这种做法
  • 这个函数接收当前的 context 值,返回一个 React 节点
<MyContext.Consumer>{value => /*基于conText值进行渲染*/}
</MyContext.Consumer>

什么时候会用到Context.Consumer?当使用value的组件是一个函数组件时、当组件中需要用到多个Context时

使用方式一

类组件中:

context/them-context.js

import React from 'react';
// 创建一个上下文
const ThemContext = React.createContext()export default ThemContext

祖先组件:classHello

import React, { Component } from 'react';
import Home from './Home';
import ThemContext from './them-context/them-context'
class classHello extends Component {render() {return (<div><ThemContext.Provider value={{color: 'red', size: '30'}}><Home /></ThemContext.Provider></div>);}
}export default classHello;

父组件:Home

import React, { Component } from 'react'
import HomeInfo from './HomeInfo'
export class Home extends Component {render() {return (<div>Home<hr /><HomeInfo /></div>)}
}export default Home

孙组件:HomeInfo

import React, { Component } from 'react'
import ThemContext from './them-context/them-context'
export class HomeInfo extends Component {render() {console.log(this.context);return (<div>HomeInfo:{this.context.color}</div>)}
}
HomeInfo.contextType = ThemContext
export default HomeInfo

函数组件中:

context/them-context.js

import React from 'react';
// 创建一个上下文
const ThemContext = React.createContext()export default ThemContext

祖先组件:

import React, { Component } from 'react';
import Home from './Home';
import ThemContext from './them-context/them-context'
class classHello extends Component {render() {return (<div><ThemContext.Provider value={{color: 'red', size: '30'}}><Home /></ThemContext.Provider></div>);}
}export default classHello;

父组件:

import React, { Component } from 'react'
import HomeInfo from './HomeInfo'
export class Home extends Component {render() {return (<div>Home<hr /><HomeInfo /></div>)}
}export default Home

孙组件:

import React from 'react'
import ThemContext from './them-context/them-context'
export default function HomeBanner() {return (<div>HomeBanner(函数组件):{<ThemContext.Consumer>{value => {return <span>{value.color}</span>}}</ThemContext.Consumer>}</div>)
}

多个context:

context/them-context.js

import React from 'react';
// 创建一个上下文
const ThemContext = React.createContext()export default ThemContext

context/user-context.js

import React from 'react';
// 创建一个上下文
const UserContext = React.createContext()export default UserContext

祖先组件:

import React, { Component } from 'react';
import Home from './Home';
import ThemContext from './context/them-context'
import UserContext from './context/user-context'
class classHello extends Component {render() {return (<div><UserContext.Provider value={{nickName: 'zs'}}><ThemContext.Provider value={{color: 'red', size: '30'}}><Home /></ThemContext.Provider></UserContext.Provider></div>);}
}export default classHello;

父组件:

import React, { Component } from 'react'
import HomeInfo from './HomeInfo'
import HomeBanner from './HomeBanner'
export class Home extends Component {render() {return (<div>Home<hr /><HomeInfo /><HomeBanner /></div>)}
}export default Home

孙组件:

import React, { Component } from 'react'
import ThemContext from './context/them-context'
import UserContext from './context/user-context'
export class HomeInfo extends Component {render() {console.log(this.context);return (<div><p>HomeInfo(类组件):{this.context.color}</p><UserContext.Consumer>{value => {return <h2>Info User:{value.nickName}</h2>}}</UserContext.Consumer></div>)}
}
HomeInfo.contextType = ThemContext
export default HomeInfo

孙组件:

import React from 'react'
import ThemContext from './context/them-context'
export default function HomeBanner() {return (<div>HomeBanner(函数组件):{<ThemContext.Consumer>{value => {return <span>{value.color}</span>}}</ThemContext.Consumer>}</div>)
}

使用方式二

步骤:

1.引入context组件
   import TestContext from '../store/testContext';
 2.使用 Xxx(模块名).Consumer 组件来创建元素
   Consumer的标签体需要一个回调函数
   它会将context设置为回调函数的参数,通过参数就可以访问到context对象

- 回调函数中,形参ctx就是Context中的对象

store/testContext.jsx

import React from "react";const TestContext = React.createContext({name: "孙悟空",age: 18,
});export default TestContext;

App.js

import React, { Component } from 'react'
import TestContext from './store/testContext';
export default class App extends Component {render() {return <TestContext.Consumer>{(ctx) => {return <div>{ctx.name} - {ctx.age}</div>}}</TestContext.Consumer>}
}

使用方式三

App.js

import React, { Component } from 'react'
import Father from './components/Father'
// 1. createContext
// 2. Provider 包裹根元素,Provider组件就是最大的根元素
const {Provider, Consumer}= React.createContext()
export { Consumer }export default class App extends Component {state = {color: 'green'}render() {return (<Provider value={this.state.color}><div><Father /></div></Provider>)}
}

components/Father.jsx

import React, { Component } from 'react'
import Son from './Son'export default class Father extends Component {render() {return (<div><h3>Father</h3><Son /></div>)}
}

components/Son.jsx

import React, { Component } from 'react'
import Sun from './Sun'
import {Consumer} from '../App'export default class Son extends Component {render() {return (<div><Consumer>{(value) => <h3>Son--{value}</h3>}</Consumer><Sun /></div>)}
}

components/Sun.jsx

import React, { Component } from 'react'
import {Consumer} from '../App'
export default class Sun extends Component {render() {return (<div><h3>Sun</h3><Consumer>{(value) => <div style={{color: value}}>孙--{value}</div>}</Consumer></div>)}
}

高级:

App.js

import React, { Component } from 'react'
import Father from './components/Father'
// 1. createContext
// 2. Provider 包裹根元素,Provider组件就是最大的根元素
const {Provider, Consumer}= React.createContext()
export { Consumer }export default class App extends Component {state = {color: 'green',bgColor: 'pink'}changeColor = (color) => {this.setState({color})}render() {const {color, bgColor} = this.statereturn (<Provider value={{color, bgColor, changeColor:this.changeColor}}><div><Father /></div></Provider>)}
}

components/Sun.jsx

import React, { Component } from 'react'
import {Consumer} from '../App'
export default class Sun extends Component {render() {return (<div><h3>Sun</h3><Consumer>{({color, bgColor, changeColor}) => <div><div style={{color, backgroundColor: bgColor}}>孙--</div><button onClick={() => changeColor('skyblue')}>修改</button></div>}</Consumer></div>)}
}

总结:

  1. 如果两个组件是远方亲戚(比如,嵌套多层)可以使用Context实现组件通讯
  1. Context提供了两个组件:Provider 和 Consumer
  1. Provider组件:用来提供数据
  1. Consumer组件:用来消费数据

对于复杂的组件通讯,将来会用  redux

状态管理方式

React-redux 或 React-mobx 状态管理方式。

对于这种方式在后面会有专门的章节去讲,这里就不去叙述了。

props深入

children属性

  • children属性:表示该组件的子节点,只要组件有子节点,props就有该属性
  • children 属性与普通的props一样,值可以是任意值(文本、React元素、组件,甚至是函数)
function Hello(props) {return (<div>该组件的子节点:{props.children}</div>)
}<Hello>我是子节点</Hello>

props校验

  • 目的:校验接收的props的数据类型,增加组件的健壮性
  • 对于组件来说,props是外来的,无法保证组件使用者传入什么格式的数据
  • 如果传入的数据格式不对,可能会导致组件内部报错。组件的使用者不能很明确的知道错误的原因
  • 更多校验知识请参考官网:https://zh-hans.reactjs.org/docs/typechecking-width-proptypes.html

  • props校验允许在创建组件的时候,就约定props的格式、类型等

  • 作用:规定接收的props的类型必须为数组,如果不是数组就会报错,增加组件的健壮性。

通过静态属性设置propTypes属性去校验外部数据的类型

propTypes是固定的单词,不能写错了,写错了校验不生效。

如果是老版本,你需要手动下载prop-types插件

yarn add prop-types
  1. 导入 prop-types 包
  1. 使用组件名.propTypes = {} 来给组件的props添加校验规则
  1. 校验规则通过 PropTypes 对象来指定
import PropTypes from "prop-types"MainBanner.propTypes = {banners: PropTypes.array.isRequired,title: PropTypes.string
}

类组件中

可以在类外部设置静态属性propTypes

import React, { Component } from 'react'
import PropTypes from 'prop-types';class PropsClass extends Component {constructor(props) {super(props);}render() {return (<div>标题:{this.props.title}</div>)}
}PropsClass.propTypes = {title: PropTypes.string.isRequired,isShow: PropTypes.bool
}export default PropsClass;

也可以在类内部设置static静态属性propTypes

import React, { Component } from 'react'
import PropTypes from 'prop-types';class PropsClass extends Component {static propTypes = {title: PropTypes.string,isShow: PropTypes.bool}constructor(props) {super(props);}render() {return (<div>标题:{this.props.title}</div>)}
}export default PropsClass;

函数组件

import React from 'react'
import PropTypes from 'prop-types';function Props(props) {console.log(props);return (<div>标题:{props.title}<br />姓名:{props.user.name}<br />年龄:{props.user.age}<br />地址:{props.user.address}</div>)
}Props.propTypes = {title: PropTypes.string.isRequired
}export default Props;

约束规则

  1. 常见类型:array、bool、func、number、object、string
  1. React元素类型:element
  1. 必填项:isRequired
  1. 特定结构的对象:shape({})
// 常见类型
optionalFunc: PropTypes.func,
// 必选
requiredFunc: PropTypes.func.isRequired,
// 特定结构的对象
optionalObjectWithShape: PropTypes.shape({color: PropTypes.string,fontSize: PropTypes.number
})

props默认值

场景:分页组件  每页显示条数
作用:给 props 设置默认值,在未传入 props 时生效

类组件中使用props默认值

  • 通过静态属性defaultProps设置外部数据的默认值
  • defaultProps这个单词是固定的,不能写错,写错了默认值不生效。

1)在类里面通过static设置静态属性

import React, { Component } from 'react'class PropsClass extends Component {static defaultProps = {title: '标题'}constructor(props) {super(props);}render() {return (<div>标题:{this.props.title}</div>)}
}
export default PropsClass;

2)可以在类外部设置静态属性--推荐

import React, { Component } from 'react'class PropsClass extends Component {constructor(props) {super(props);}render() {return (<div>标题:{this.props.title}</div>)}
}PropsClass.defaultProps = {title: '标题'
}export default PropsClass;

这两种方式是等价的,都是静态属性。

函数组件中使用props默认值

  • 外部数据props的默认值,需要在组件上添加静态属性defaultProps
  • defaultProps这个单词是固定的,不能写错,写错了默认值不生效。

import React from 'react'function Props(props) {return (<div>标题:{props.title}<br />姓名:{props.user.name}<br />年龄:{props.user.age}<br />地址:{props.user.address}</div>)
}Props.defaultProps = {title: '标题'
}
export default Props;

组件性能优化

  1. 功能第一
  1. 性能优化

减轻state

  • 减轻 state:只存储跟组件渲染相关的数据(比如:count / 列表数据 / loading 等)
  • 注意:不用做渲染的数据不要放在 state 中,比如定时器 id等
  • 对于这种需要在多个方法中用到的数据,应该直接放在 this 中
    • this.xxx = 'bbb'
    • this.xxx

class Hello extends Component {componentDidMount() {// timerId存储到this中,而不是state中this.timerId = setInterval(() => {}, 2000)}componentWillUnmount() {clearInterval(this.timerId)}render() { … }
}

vue中不要把和渲染无关的数据放到data中

避免不必要的重新渲染

  • 组件更新机制:父组件更新会引起子组件也被更新,这种思路很清晰
  • 问题:子组件没有任何变化时也会重新渲染 (接收到的props没有发生任何的改变)
  • 如何避免不必要的重新渲染呢?
  • 解决方式:使用钩子函数 shouldComponentUpdate(nextProps, nextState)
  • 作用:通过返回值决定该组件是否重新渲染,返回 true 表示重新渲染,false 表示不重新渲染
  • 触发时机:更新阶段的钩子函数,组件重新渲染前执行 (shouldComponentUpdate => render)

class Hello extends Component {shouldComponentUpdate() {// 根据条件,决定是否重新渲染组件return false}render() {…}
}

案例:随机数

纯组件

  • 纯组件:React.PureComponentReact.Component功能相似
  • 区别:PureComponent 内部自动实现了 shouldComponentUpdate 钩子,不需要手动比较
  • 原理:纯组件内部通过分别 对比 前后两次 props 和 state 的值,来决定是否重新渲染组件

class Hello extends React.PureComponent {render() {return (<div>纯组件</div>)}
}

只有在性能优化的时候可能会用到纯组件,不要所有的组件都使用纯组件,因为纯组件需要消耗性能进行对比

纯组件比较-值类型

  • 说明:纯组件内部的对比是 shallow compare(浅层对比)
  • 对于值类型来说:比较两个值是否相同(直接赋值即可,没有坑)

let number = 0
let newNumber = number
newNumber = 2
console.log(number === newNumber) // false

state = { number: 0 }
setState({number: Math.floor(Math.random() * 3)
})
// PureComponent内部对比:
最新的state.number === 上一次的state.number // false,重新渲染组件

纯组件比较-引用类型

  • 说明:纯组件内部的对比是 shallow compare(浅层对比)
  • 对于引用类型来说:只比较对象的引用(地址)是否相同

const obj = { number: 0 }
const newObj = obj
newObj.number = 2
console.log(newObj === obj) // true

state = { obj: { number: 0 } }
// 错误做法
state.obj.number = 2
setState({ obj: state.obj })
// PureComponent内部比较:
最新的state.obj === 上一次的state.obj // true,不重新渲染组件

纯组件的最佳实践:

注意:state 或 props 中属性值为引用类型时,应该创建新数据,不要直接修改原数据!

// 正确!创建新数据
const newObj = {...state.obj, number: 2}
setState({ obj: newObj })
// 正确!创建新数据
// 不要用数组的push / unshift 等直接修改当前数组的的方法
// 而应该用 concat 或 slice 等这些返回新数组的方法
this.setState({list: [...this.state.list, {新数据}]
})

http://www.ppmy.cn/server/119824.html

相关文章

Isaac Sim 跑Slam学习过程2024.9.20

# 本文随着时间逐渐增加内容&#xff0c;是学习笔记 # 诶怎么今天Isaac Sim 4.2.0 突然出现了&#xff0c;这哪来的时间再去试试新的.... 没有大佬带...自己学吧 希望使用仿真环境跑定位Slam&#xff0c;现在IMU在Isaac Sim中有现成的传感器模块&#xff0c;GPS则没有&am…

【Verilog学习日常】—牛客网刷题—Verilog快速入门—VL18

实现3-8译码器① 描述 下表是74HC138译码器的功能表. E3 E2_n E1_n A2 A1 A0 Y0_n Y1_n Y2_n Y3_n Y4_n Y5_n Y6_n Y7_n x 1 x x x x 1 1 1 1 1 1 1 1 x x 1 x x x 1 1 1 1 1 1 1 1 0 x x x x x 1 1 1 1 1 1 1 1 1 …

Windows10、CentOS Stream9 环境下安装kafka_2.12-3.6.2记录

目录 Windows下操作1. 安装kafka [kafka_2.12-3.6.2](https://downloads.apache.org/kafka/3.6.2/kafka_2.12-3.6.2.tgz)2. 启动Zookeeper2.1 进入Kafka的config目录&#xff0c;修改zookeeper.properties配置文件 3. 启动kafka3.1 进入Kafka的config目录&#xff0c;修改serve…

C语言循环学习

作为初学者&#xff0c;学习C语言中的循环结构是非常重要的&#xff0c;它们能让你轻松地重复执行代码。在C语言中&#xff0c;常用的循环结构主要有for循环和while循环。我们将从基本概念开始&#xff0c;逐步讲解如何使用这两种循环&#xff0c;并通过示例帮助你理解和练习。…

解决ubuntu22.04 gnome-terminal 无法启动的问题

22.04下面默认的python 版本是3.10. 如果你安装了3.8或其它版本&#xff0c;尽量不要去ln -s python3.8 python3修改默认python3版本&#xff0c;不然Terminal会打不开。猜测Terminal可能用到了python的_gi这个库。 可以在xterm或putty远程连上&#xff0c;输入 sudo gnome-te…

96 kHz、24bit 立体声音频ADC芯片GC5358描述

概述&#xff1a; GC5358 是一款高性能、宽采样率、立体声音频模数转换器。其采样率范围是8KHz~96KHz&#xff0c;非常适合从消费级到专业级的音频应用系统。单端模拟输入不需要外围器件。GC5358 音频有两种数据格式&#xff1a;MSB对齐和 I2S 格式&#xff0c;和各种如 DTV、D…

98-策略模式的理解

‌策略模式是一种软件设计模式&#xff0c;它定义了一系列算法&#xff0c;并将每个算法封装起来&#xff0c;使它们可以相互替换。这种模式允许算法的变化不会影响使用算法的客户端&#xff0c;通过将使用算法的责任和算法的实现分割开来&#xff0c;并委派给不同的对象对这些…

java日志框架之JUL(Logging)

文章目录 一、JUL简介1、JUL组件介绍 二、Logger快速入门三、Logger日志级别1、日志级别2、默认级别info3、原理分析4、自定义日志级别5、日志持久化&#xff08;保存到磁盘&#xff09; 三、Logger父子关系四、Logger配置文件 一、JUL简介 JUL全程Java Util Logging&#xff…