React进阶之路(二)-- 组件通信、组件进阶-CSDN博客

  • 阿里云国际版折扣https://www.yundadi.com

  • 阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

    文章目录

    组件通信

    组件通信的意义

    组件是独立且封闭的单元默认情况下组件只能使用自己的数据state
    组件化开发的过程中完整的功能会拆分多个组件在这个过程中不可避免的需要互相传递一些数据
    为了能让各组件之间可以进行互相沟通数据传递这个过程就是组件通信

    1. 父子关系 - 最重要的
    2. 兄弟关系 - 自定义事件模式产生技术方法 eventBus / 通过共同的父组件通信
    3. 其它关系 - mobx / redux / zustand

    父传子实现

    实现步骤

    1. 父组件提供要传递的数据 - state 可以不满足例如传递方法的时候
    2. 给子组件标签添加属性值为 state中的数据
    3. 子组件中通过 props 接收父组件中传过来的数据
      • 类组件使用this.props获取props对象
      • 函数式组件直接通过参数获取props对象

    在子组件标签上添加的属性都会成为该子组件props属性对象上的值

    接下来我们来实现一下如下的场景

    在这里插入图片描述
    实现代码

    import React from 'react'
    
    // 函数式子组件
    function FSon(props) {
      console.log(props)
      return (
        <div>
          子组件1
          {props.msg}
        </div>
      )
    }
    
    // 类子组件
    class CSon extends React.Component {
      render() {
        return (
          <div>
            子组件2
            {this.props.msg}
          </div>
        )
      }
    }
    // 父组件
    class App extends React.Component {
      state = {
        message: 'this is message'
      }
      render() {
        return (
          <div>
            <div>父组件</div>
            <FSon msg={this.state.message} />
            <CSon msg={this.state.message} />
          </div>
        )
      }
    }
    
    export default App
    

    props说明

    1. props是只读对象readonly
      根据单项数据流的要求子组件只能读取props中的数据不能进行修改

    2. props可以传递任意数据
      数字、字符串、布尔值、数组、对象、函数JSX

    class App extends React.Component {
      state = {
        message: 'this is message'
      }
      render() {
        return (
          <div>
            <div>父组件</div>
            <FSon 
              msg={this.state.message} 
              age={20} 
              isMan={true} 
              cb={() => { console.log(1) }} 
              child={<span>this is child</span>}
            />
            <CSon msg={this.state.message} />
          </div>
        )
      }
    }
    

    在这里插入图片描述
    同时我们在使用props的时候通常会使用解构赋值例如

    在这里插入图片描述

    当然我们也可以直接在函数的参数位置进行解构赋值

    在这里插入图片描述

    子传父实现

    本质 父组件给子组件传递回调函数子组件调用

    实现步骤

    1. 父组件提供一个回调函数 - 用于接收数据
    2. 将函数作为属性的值传给子组件
    3. 子组件通过props调用 回调函数
    4. 将子组件中的数据作为参数传递给回调函数

    接下来我们来实现一下如下的场景
    在这里插入图片描述

    代码实现

    import React from 'react'
    
    // 子组件
    function Son(props) {
      function handleClick() {
        // 调用父组件传递过来的回调函数 并注入参数
        props.changeMsg('this is newMessage')
      }
      return (
        <div>
          {props.msg}
          <button onClick={handleClick}>change</button>
        </div>
      )
    }
    
    
    class App extends React.Component {
      state = {
        message: 'this is message'
      }
      // 提供回调函数
      changeMessage = (newMsg) => {
        console.log('子组件传过来的数据:',newMsg)
        this.setState({
          message: newMsg
        })
      }
      render() {
        return (
          <div>
            <div>父组件</div>
            <Son
              msg={this.state.message}
              // 传递给子组件
              changeMsg={this.changeMessage}
            />
          </div>
        )
      }
    }
    
    export default App
    

    兄弟组件通信

    核心思路 通过状态提升机制利用共同的父组件实现兄弟通信。也就是说通过子传父+父传子来实现。

    在这里插入图片描述

    实现步骤

    1. 将共享状态提升到最近的公共父组件中由公共父组件管理这个状态
      • 提供共享状态
      • 提供操作共享状态的方法
    2. 要接收数据状态的子组件通过 props 接收数据
    3. 要传递数据状态的子组件通过props接收方法调用方法传递数据

    代码实现

    import React from 'react'
    
    // 子组件A
    function SonA(props) {
      return (
        <div>
          SonA
          {props.msg}
        </div>
      )
    }
    // 子组件B
    function SonB(props) {
      return (
        <div>
          SonB
          <button onClick={() => props.changeMsg('new message')}>changeMsg</button>
        </div>
      )
    }
    
    // 父组件
    class App extends React.Component {
      // 父组件提供状态数据
      state = {
        message: 'this is message'
      }
      // 父组件提供修改数据的方法
      changeMsg = (newMsg) => {
        this.setState({
          message: newMsg
        })
      }
    
      render() {
        return (
          <>
            {/* 接收数据的组件 */}
            <SonA msg={this.state.message} />
            {/* 修改数据的组件 */}
            <SonB changeMsg={this.changeMsg} />
          </>
        )
      }
    }
    
    export default App
    

    跨组件通信Context

    在这里插入图片描述
    上图是一个react形成的嵌套组件树如果我们想从App组件向任意一个下层组件传递数据该怎么办呢目前我们能采取的方式就是一层一层的props往下传显然很繁琐

    那么Context 提供了一个无需为每层组件手动添加 props就能在组件树间进行数据传递的方法

    实现步骤

    1. 创建Context对象 导出 Provider 和 Consumer对象

      const { Provider, Consumer } = createContext()
      
    2. 使用Provider包裹上层组件提供数据 注意Provider组件不一定必须包裹根组件但是必须包裹需要共享数据的子组件的最近公共祖先组件。这样Provider组件可以将数据通过Context对象传递给所有的子孙组件而不需要通过props逐层传递

      <Provider value={this.state.message}>
          {/* 根组件 */}
      </Provider>
      
    3. 需要用到数据的组件使用Consumer包裹获取数据 注意这里括号里面是一个箭头函数的方法声明形式不能改变

      <Consumer >
          {value => /* 基于 context 值进行渲染*/}
      </Consumer>
      

    代码实现

    import React, { createContext }  from 'react'
    
    // 1. 创建Context对象 
    const { Provider, Consumer } = createContext()
    
    
    // 3. 消费数据
    function ComC() {
      return (
        <Consumer >
          {value => <div>{value}</div>}
        </Consumer>
      )
    }
    
    function ComA() {
      return (
        <ComC/>
      )
    }
    
    // 2. 提供数据
    class App extends React.Component {
      state = {
        message: 'this is message'
      }
      render() {
        return (
          <Provider value={this.state.message}>
            <div className="app">
              <ComA />
            </div>
          </Provider>
        )
      }
    }
    
    export default App
    

    通信案例

    要求App为父组件用来提供列表数据 ListItem为子组件用来渲染列表数据

    在这里插入图片描述

    // 列表数据
    [
      { id: 1, name: '超级好吃的棒棒糖', price: 18.8, info: '开业大酬宾全场8折' },
      { id: 2, name: '超级好吃的大鸡腿', price: 34.2, info: '开业大酬宾全场8折' },
      { id: 3, name: '超级无敌的冰激凌', price: 14.2, info: '开业大酬宾全场8折' }
    ]
    

    代码实现

    import React from 'react'
    
    // 子组件
    function ListItem(props) {
      const { name, price, info, id, delHandler } = props
      return (
        <div>
          <h3>{name}</h3>
          <p>{price}</p>
          <p>{info}</p>
          <button onClick={() => delHandler(id)}>删除</button>
        </div>
      )
    }
    
    // 父组件
    class App extends React.Component {
      state = {
        list: [
          { id: 1, name: '超级好吃的棒棒糖', price: 18.8, info: '开业大酬宾全场8折' },
          { id: 2, name: '超级好吃的大鸡腿', price: 34.2, info: '开业大酬宾全场8折' },
          { id: 3, name: '超级无敌的冰激凌', price: 14.2, info: '开业大酬宾全场8折' }
        ]
      }
    
      delHandler = (id) => {
        this.setState({
          list: this.state.list.filter(item => item.id !== id)
        })
      }
    
      render() {
        return (
          <>
            {
              this.state.list.map(item =>
                <ListItem
                  key={item.id}
                  {...item}
                  delHandler={this.delHandler} 
                />
              )
            }
          </>
        )
      }
    }
    
    export default App
    

    React组件进阶

    children属性

    children属性表示该组件的子节点只要组件内部有子节点props中就有该属性

    children可以是

    1. 普通文本
    2. 普通标签元素
    3. 函数 / 对象
    4. JSX

    props校验

    在ts中这个问题得到了很好的解决不需要第三方依赖的支持。

    对于组件来说props是由外部传入的我们其实无法保证组件使用者传入了什么格式的数据如果传入的数据格式不对就有可能会导致组件内部错误有一个点很关键 - 组件的使用者可能报错了也不知道为什么看下面的例子
    在这里插入图片描述

    面对这样的问题如何解决 props校验

    实现步骤

    1. 安装属性校验包yarn add prop-types
    2. 导入prop-types
    3. 使用 组件名.propTypes = {} 给组件添加校验规则

    核心代码

    import PropTypes from 'prop-types'
    
    const List = props => {
      const arr = props.colors
      const lis = arr.map((item, index) => <li key={index}>{item.name}</li>)
      return <ul>{lis}</ul>
    }
    
    List.propTypes = {
      colors: PropTypes.array
    }
    

    规则说明

    四种常见结构

    1. 常见类型array、bool、func、number、object、string
    2. React元素类型element
    3. 必填项isRequired
    4. 特定的结构对象shape({})
    // 常见类型
    optionalFunc: PropTypes.func,
    // 必填 只需要在类型后面串联一个isRequired
    requiredFunc: PropTypes.func.isRequired,
    // 特定结构的对象
    optionalObjectWithShape: PropTypes.shape({
    	color: PropTypes.string,
    	fontSize: PropTypes.number
    })
    

    官网文档更多阅读https://reactjs.org/docs/typechecking-with-proptypes.html

    如何给组件的props提供默认值

    对于函数组件来说

    直接使用函数参数默认值

    function List({pageSize = 10}) {
      return (
        <div>
          此处展示props的默认值{ pageSize }
        </div>
      )
    }
    
    // 不传入pageSize属性
    <List />
    

    对于类组件来说

    使用类静态属性声明默认值static defaultProps = {}

    class List extends Component {
      static defaultProps = {
        pageSize: 10
      }
      render() {
        return (
          <div>
            此处展示props的默认值{this.props.pageSize}
          </div>
        )
      }
    }
    <List />
    

    通过我们上面使用的第三包工具包里的 defaultProps 也可以给组件的props设置默认值在未传入props的时候生效

    组件生命周期

    组件的生命周期是指组件从被创建到挂载到页面中运行起来再到组件不用时卸载的过程注意只有类组件才有生命周期类组件 实例化 函数组件 不需要实例化

    那么我们自然就有一个疑问既然函数组件没有生命周期那么他怎么实现类似钩子函数的功能

    • React函数组件本身是没有生命周期函数的因为它们只是接收props并返回JSX的纯函数没有自己的状态和实例。
    • React函数组件可以通过使用Effect Hook来模拟一些生命周期的功能例如在组件挂载、更新或卸载时执行一些副作用操作¹。
    • React函数组件还可以通过使用其他的Hook来实现一些类组件的特性例如useState Hook可以让函数组件拥有自己的状态useRef Hook可以让函数组件访问DOM元素或保存一些可变的值useCallback Hook和useMemo Hook可以让函数组件优化性能等。

    在这里插入图片描述
    如何理解render阶段纯净且不包含副作用

    • React的render阶段是指React组件根据props和state生成虚拟DOM的过程。在这个过程中React会调用组件的render方法或函数组件本身返回一个React元素或null
    • React的render阶段是纯净的也就是说它不会对组件的props和state进行任何修改也不会对外部环境产生任何影响。这样可以保证render阶段的可预测性和可测试性以及避免不必要的渲染和性能损耗。
    • React的render阶段是不包含副作用的也就是说它不会对真实的DOM进行任何操作也不会触发任何生命周期方法或钩子函数。这样可以保证render阶段的纯粹性和高效性以及避免不必要的副作用和错误。
      • 例如发送一个请求是一种副作用操作因为它会对外部环境产生影响例如改变服务器的状态或者获取数据。因此发送一个请求不应该在render阶段进行而应该在其他阶段进行例如在生命周期方法或钩子函数中。
    • React的render阶段只负责生成虚拟DOM而不负责渲染到真实的DOM上。这一步由React的commit阶段完成它会根据虚拟DOM和真实DOM的差异进行最小化的更新。在commit阶段React会执行一些副作用操作例如调用生命周期方法或钩子函数插入或删除DOM节点添加或移除事件监听器等。

    接下来我们来看一下类组件的三个重要的生命阶段

    • 挂载
    • 更新
    • 卸载

    挂载阶段

    在这里插入图片描述
    注意

    • constructer现在用的不是很多了例如初始化state直接写就行不用写在constructer里面。
    • 在render里面不要使用setState因为setState会导致重新渲染从而再次执行render函数相当于进入了无限循环。

    例如
    在这里插入图片描述
    在这里插入图片描述

    更新阶段

    也就是每次更新都会执行的钩子函数
    在这里插入图片描述
    注意

    • componentDidUpdate不能直接调用setState因为setState会造成更新从而再次调用render函数这样就会造成无限循环。

    卸载阶段

    在这里插入图片描述

    在这里插入图片描述

    注意react类组件中的生命周期函数可以使用箭头函数表示例如

    // 导入React和Component
    import React, { Component } from "react";
    
    // 定义一个计数器组件继承自Component
    class Counter extends Component {
      // 定义一个构造函数用来初始化state和props
      constructor(props) {
        // 调用super方法传入props
        super(props);
        // 初始化state设置count为0
        this.state = {
          count: 0,
        };
      }
    
      // 定义一个生命周期函数用来在组件挂载时执行一些操作
      // 使用箭头函数不需要使用bind方法或者闭包来绑定this
      componentDidMount = () => {
        // 在控制台输出一条信息表示组件已经挂载
        console.log("The component is mounted.");
      };
    
      // 定义一个生命周期函数用来在组件更新时执行一些操作
      // 使用箭头函数不需要使用bind方法或者闭包来绑定this
      componentDidUpdate = () => {
        // 在控制台输出一条信息表示组件已经更新以及当前的count值
        console.log("The component is updated. The count is " + this.state.count);
      };
    
      // 定义一个生命周期函数用来在组件卸载时执行一些操作
      // 使用箭头函数不需要使用bind方法或者闭包来绑定this
      componentWillUnmount = () => {
        // 在控制台输出一条信息表示组件已经卸载
        console.log("The component is unmounted.");
      };
    
      // 定义一个方法用来增加count的值
      // 使用箭头函数不需要使用bind方法或者闭包来绑定this
      increment = () => {
        // 使用setState方法将count的值加一
        this.setState((prevState) => ({
          count: prevState.count + 1,
        }));
      };
    
      // 定义一个方法用来减少count的值
      // 使用箭头函数不需要使用bind方法或者闭包来绑定this
      decrement = () => {
        // 使用setState方法将count的值减一
        this.setState((prevState) => ({
          count: prevState.count - 1,
        }));
      };
    
      // 定义一个渲染方法用来返回组件的视图
      render() {
        // 返回一个div元素包含一个h1元素和一个button元素
        return (
          <div>
            <h1>Counter: {this.state.count}</h1>
            <button onClick={this.increment}>+</button>
            <button onClick={this.decrement}>-</button>
          </div>
        );
      }
    }
    
    // 导出Counter组件
    export default Counter;
    
    

    还要注意生命周期函数是同步的只有在执行完了之后才会进入下一个生命周期执行下一个生命周期钩子。

  • 阿里云国际版折扣https://www.yundadi.com

  • 阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6