• 组件间通讯及钩子函数


    组件通讯介绍

    组件是独立且封闭的单元,默认情况下,只能使用组件自己的数据。在组件化过程中,我们将一个完整的功能拆分成多个组件,以更好的完成整个应用的功能。而在这个过程中,多个组件之间不可避免的要共享某些数据。为了实现这些功能,就需要打破组件的独立封闭性,让其与外界沟通。这个过程就是组件通讯

    组件的props

    • 组件是封闭的,要接收外部数据应该通过props来实现

    • props的作用:接收传递给组件的数据

    • 传递数据:给组件标签添加属性

    • 接收数据:函数组件通过参数props接收数据,类组件通过this.props接收数据

    • 示例:

      /*
      * props  函数组件
      * */
      const Hello = props => {
          console.log(props);
          return (
              <div>
                  props: {props.name}
              </div>
          )
      }
      
      ReactDOM.render(<Hello name='xiaohao' age={18}/>, document.getElementById('root'))
      
      /*
      * props  类组件
      * */
      
      class Hello extends React.Component {
          render(){
              console.log(this.props)
              return (
                  <div>
                      props: {this.props.name}
                  </div>
              )
          }
      }
      
      
      ReactDOM.render(<Hello name='xiaohao' age={18}/>, document.getElementById('root'))
      
      
    • 特点:

      • 1、可以给组件传递任意类型的数据

        • 示例:

          class Hello extends React.Component {
              
              render(){
                  console.log(this.props)
                  this.props.fn()
                  return (
                      <div>
                          props: {this.props.name}
                          {this.props.zujian}
                      </div>
                  )
              }
          }
          
          
          ReactDOM.render(
              <Hello 
              name='xiaohao' 
              age={18}
              list={[
                  1, 2, 3
              ]}
              fn = {() => console.log('这是一个函数')}
              zujian = {<p>这是一个p标签</p>}
              />, document.getElementById('root')
          )
          
      • 2、props只读的对象,只能读取属性的值,无法修改对象

      • 3、注意:使用类组件时,如果写了构造函数,应该将props传递给super(),否则,无法在构造函数中获取到props

        • 示例:

          class Hello extends React.Component {
          
              constructor(props){
                  super(props)
                  console.log(props);
              }
              
              render(){
                  console.log(this.props)
                  this.props.fn()
                  return (
                      <div>
                          props: {this.props.name}
                          {this.props.zujian}
                      </div>
                  )
              }
          }
          
          
          ReactDOM.render(
              <Hello 
              name='xiaohao' 
              age={18}
              list={[
                  1, 2, 3
              ]}
              fn = {() => console.log('这是一个函数')}
              zujian = {<p>这是一个p标签</p>}
              />, document.getElementById('root')
          )
          

    组件通讯的三种方式

    • 1、父组件 -> 子组件

      • 1、父组件提供要传递的state数据

      • 2、给子组件标签添加属性,值为state中的数据

      • 3、子组件中通过props接收父组件中传递的数据

      • 示例:

        class Parent extends React.Component {
            state = {
                lastName: '顾'
            } 
            render(){
                return (
                    <div className="parent">
                        父组件:
                        <Child name={this.state.lastName}/>
                    </div>
                )
            }
        }
        
        const Child = props => {
            console.log(props);
            return (
                <div className="child">
                    <p>子组件接收到父组件的数据:{props.name}</p>
                </div>
            )
        }
        
        ReactDOM.render(<Parent/>, document.getElementById('root'))
        
    • 2、子组件 -> 父组件

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

      • 1、父组件提供一个回调函数(用于接收数据)

      • 2、将该函数作为属性的值,传递给子组件

      • 3、子组件通过props调用回调函数

      • 4、将子组件的数据作为参数传递给回调函数

      • 示例:

        class Parent extends React.Component {
            state = {
                msg: ''
            }
            getChildMsg = data => {
                console.log('接收到儿子传来的消息', data);
                this.setState({
                    msg: data
                })
            }
            render(){
                return (
                    <div className="parent">
                        父组件:{this.state.msg}
                        <Child getMsg={this.getChildMsg}/>
                    </div>
                )
            }
        }
        
        class Child extends React.Component {
            state = {
                msg: '刷抖音'
            }
            
            handleClick = () => {
                // 子组件调用父组件中传过来的回调函数
                this.props.getMsg(this.state.msg)
            }
            render(){
                return (
                    <div className="child">
                        子组件:
                        <button onClick={this.handleClick}>点我</button>
                    </div>
                )
            }
        }
        
        ReactDOM.render(<Parent/>, document.getElementById('root'))
        
      • 注意:回调函数中this指向问题!!!

    • 3、兄弟组件

      • 共享状态提升到最近的公共父组件中,由公共父组件管理这个状态

      • 思想:状态提升

      • 公共父组件职责:1、提供共享状态 2、提供操作共享状态的方法

      • 要通讯的子组件只需通过props接收状态或操作状态的方法

      • 示例:

        class Content extends React.Component {
        
            state = {
                count: 0
            }
        
            onIncrement = () => {
                this.setState({
                    count: this.state.count + 1
                })
            }
        
            render(){
                return (
                    <div>
                        <Child1 count={this.state.count}/>
                        <Child2 onIncrement={this.onIncrement}/>
                    </div>
                )
            }
        }
        
        
        class Child1 extends React.Component {
            render(){
                return (
                    <h1>计数器:{this.props.count}</h1>
                )
            }
        }
        
        class Child2 extends React.Component {
            render(){
                return (
                    <button onClick={this.props.onIncrement}>+1</button>   
                )
            }
        }
        
        ReactDOM.render(<Content/>, document.getElementById('root'))
        

    Context

    • 更好的姿势:使用Context

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

    • 使用步骤:

      • 1、调用React.createContext()创建Provider(提供数据)和Consumer(消费数据)两个组件。

      • 2、使用Provider组件作为父节点。

      • 3、设置value属性,表示要传递的数据。

      • 4、调用Consumer组件接收数据。

      • 示例:

        const { Provider, Consumer } = React.createContext()
        
        class App extends React.Component {
            render (){
                return (
                    <Provider value="小浩">
                        <div>
                            父组件
                            <Node/>
                        </div>
                    </Provider>
                )
            }
        }
        
        const Node = props => {
            return (
                <div className="node">
                    <SubNode/>
                </div>
            )
        }
        
        const SubNode = props => {
            return (
                <div className="subnode">
                    <Child/>
                </div>
            )
        }
        
        const Child = props => {
            return (
                <div className="child">
                    我是子组件
                    <Consumer>
                        {data => <span>我是子节点:{data}</span>}
                    </Consumer>
                </div>
            )
        }
        
        ReactDOM.render(<App />, document.getElementById('root'))
        
      • 总结:

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

    props深入

    • children属性

      • children属性:表示组件标签的子节点。当组件标签有子节点时,props就会有该属性。

      • children属性与普通的props一样,值可以是任意值(文本、React元素、组件,甚至是函数)。

      • 示例:

        // const App = props => {
        //     console.log(props);
        //     return (
        //         <div>
        //             <h1>组件标签的子节点:</h1>
        //             {props.children}
        //         </div>
        //     )
        // }
        
        // ReactDOM.render(<App>我是app</App>, document.getElementById('root'))
        
        
        // const App = props => {
        //     console.log(props);
        //     return (
        //         <div>
        //             <h1>组件标签的子节点:</h1>
        //             {props.children}
        //         </div>
        //     )
        // }
        
        // ReactDOM.render(<App><p>我是一个p标签</p></App>, document.getElementById('root'))
        
        
        const App = props => {
            console.log(props);
            props.children()
            return (
                <div>
                    <h1>组件标签的子节点:</h1>
                </div>
            )
        }
        
        ReactDOM.render(<App>{() => console.log('我是一个函数')}</App>, document.getElementById('root'))
        
        
    • props校验

      • 对于组件来说,props是外来的,无法保证组件使用者传入什么格式的数据。

      • 如果传入的数据格式不对,可能会导致组件内部报错

      • 关键问题:组件的使用者不知道明确的错误原因

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

      • 作用:捕获使用组件时因为props导致的错误,给出明确的错误提示,增加组件的健壮性

      • 使用步骤:

        • 1、安装包prop-types(yarn add prop-types / npm i props-types )

        • 2、导入prop-types包

        • 3、使用组件名:propTypes={}来给组件的props添加校验规则

        • 4、校验规则通过PropTypes对象来指定

        • 示例:

          import PropTypes from 'prop-types'
          const App = props => {
              const arr = props.colors
              const lis = arr.map((item, index) => (<li key={index}>{item.name}</li>))
              return <ul>{lis}</ul>
          }
          App.propTypes = {
              colors: PropTypes.array
          }
          
          ReactDOM.render(<App colors={[{name: 'niu'}]} />, document.getElementById('root'))
          
      • 约束规则

        • 1、常见类型:array、bool、func、number、object、string

        • 2、React元素类型:element

        • 3、必填项:isRequired

        • 4、特定结构的对象:shape({})

        • 示例:

          import PropTypes from 'prop-types'
          const App = props => {
              return (
                  <div>
                      <h1> props校验</h1>
                  </div>
              )
          }
          // 添加props校验
          // 属性 a 的类型:数值(number)
          // 属性 fn 的类型:函数(func)并且为必填项
          // 属性 tag 的类型: React元素(element)
          // 属性 filter 的类型: 对象({area: '上海', price: 1999})
          App.propTypes = {
              a: PropTypes.number,
              fn: PropTypes.func.isRequired,
              tag: PropTypes.element,
              filter: PropTypes.shape({
                  area: PropTypes.string,
                  price: PropTypes.number
              })
          }
          
          ReactDOM.render(<App fn={() => console.log('校验函数')} />, document.getElementById('root'))
          
      • props的默认值

        • 场景:分页组件 -> 每页显示条数

        • 作用:给props设置默认值,在未传入props时生效

        • 示例:

          const App = props => {
              console.log(props);
              return (
                  <div>
                      <h1> props默认值 {props.pageSize}</h1>
                  </div>
              )
          }
          
          // 添加props默认值
          App.defaultProps = {
              pageSize: 10
          }
          
          ReactDOM.render(<App  />, document.getElementById('root'))
          

    组件的生命周期

    • 意义:组件的生命周期有助于理解组件的运行方式、完成更复杂的组件功能、分析组件错误原因等

    • 组件的生命周期:组件从被创建到挂载到页面中运行,再到组件不用时卸载的过程

    • 生命周期的每个阶段总是伴随着一些方法调用,这些方法就是生命周期的钩子函数

    • 钩子函数的作用:为开发人员在不同阶段操作组件提供了时机。

    • 只有类组件才有生命周期

    • 生命周期的三个阶段

      • 1、每个阶段的执行时机

        • 1、创建时(挂载阶段)

          • 执行时机:组件创建时(页面加载时)

          • 执行顺序:constructor() -> render() -> compoentDidMount()

          • 示例:

            class App extends React.Component {
                constructor(props){
                    super(props);
                    console.warn('钩子函数 constructor');
                    this.state = {
                        count: 0
                    }
            
                }
            
                render(){
                    console.warn('钩子函数 render');
            
                    // 错误演示 不要在render中调用setState方法
                    // this.setState({
                    //     count: 1
                    // })
            
                    return (
                        <div id="title">
                            render
                        </div>
                    )
                }
            
                componentDidMount(){
                    console.warn('钩子函数 componentDidMount');
                    const title = document.getElementById('title')
                    console.log(title);
                }
            
            }
            
            ReactDOM.render(<App  />, document.getElementById('root'))
            
        • 2、更新时(更新阶段)

          • 执行时机:1、setState() 2、forceUpdate() 3、组件接收到新的props

          • 说明:以上三者任意一种变化,组件就会重新渲染

          • 示例:

            class App extends React.Component {
                constructor(props){
                    super(props);
                    this.state = {
                        count: 0
                    }
            
                }
                onIncrement = () => {
                    // this.setState({
                    //     count: this.state.count + 1
                    // })
            
                    // 强制更新
                    this.forceUpdate()
                }
            
                render(){
                    console.warn('钩子函数 render');
                    return (
                        <div>
                            <Child count={this.state.count}/>
                            <button onClick={this.onIncrement}>+1</button>
                        </div>
                    )
                }
            
            }
            
            class Child extends React.Component {
            
                render(){
                    console.warn('子组件----钩子函数 render');
                    return (
                        <div>
                            计数:{this.props.count}
                        </div>
                    )
                }
            
            }
            
            
            ReactDOM.render(<App  />, document.getElementById('root'))
            
          • 执行顺序:render() -> componentDidUpdate()

          • 示例:

            class App extends React.Component {
                constructor(props){
                    super(props);
                    this.state = {
                        count: 0
                    }
            
                }
                onIncrement = () => {
                    this.setState({
                        count: this.state.count + 1
                    })
                }
            
                render(){
                    console.warn('钩子函数 render');
                    return (
                        <div>
                            <Child count={this.state.count}/>
                            <button onClick={this.onIncrement}>+1</button>
                        </div>
                    )
                }
            
            }
            
            class Child extends React.Component {
            
                render(){
                    console.warn('子组件----钩子函数 render');
                    return (
                        <div id='child'>
                            计数:{this.props.count}
                        </div>
                    )
                }
            
                // 注意:如果要调用setState()更新状态,必须要放在一个if条件中
                // 因为:如果直接调用setState()更新状态,也会导致递归
                componentDidUpdate(prevProps){
                    console.log('更新前数据', prevProps, '更新后数据', this.props);
                    console.warn('子组件----钩子函数 componentDidUpdate');
                    
                    // const child = document.getElementById('child')
                    // console.log(child.innerHTML);
            
                    // 错误示范
                    // this.setState({})
            
                    // 正确示范
                    if(prevProps.count !== this.props.count){
                        // 发送ajax请求
                        this.setState({})
                    }
                }
            
            }
            
            
            ReactDOM.render(<App  />, document.getElementById('root'))
            
        • 3、卸载时(卸载阶段)

          • 执行时机:组件从页面中消失

          • 示例:

            class App extends React.Component {
                constructor(props){
                    super(props);
                    this.state = {
                        count: 0
                    }
            
                }
                onIncrement = () => {
                    this.setState({
                        count: this.state.count + 1
                    })
                }
            
                render(){
                    console.warn('钩子函数 render');
                    return (
                        <div>
                            {this.state.count >= 3 ? <p>到3la</p> : (<Child count={this.state.count}/>)}
                            <button onClick={this.onIncrement}>+1</button>
                        </div>
                    )
                }
            
            }
            
            class Child extends React.Component {
            
                componentDidMount(){
                    // 创建定时器
                    this.timeId = setInterval(()=>{
                        console.log('定时器正在运行');
                    }, 500)
                }
            
                render(){
                    console.warn('子组件----钩子函数 render');
                    return (
                        <div id='child'>
                            计数:{this.props.count}
                        </div>
                    )
                }
            
                componentWillUnmount(){
                    console.warn('子组件----钩子函数 componentWillUnmount');
            
                    // 清洗计时器
                    clearInterval(this.timeId)
                }
            
            
            }
            
            
            ReactDOM.render(<App  />, document.getElementById('root'))
            
            
      • 2、每个阶段钩子函数的执行顺序

      • 3、每个阶段钩子函数的作用

      • 不常用钩子函数介绍

        • 新版完成生命周期钩子函数

    render-props和高阶组件

    • React组件复用概述

      • 思考:如果两个组件中的部分功能相似或相同,该如何处理?
      • 处理方式:复用相似的功能(联想函数封装)
      • 复用什么? 1、state 2、操作state的方法(组件状态逻辑)
      • 两种方式:1、render props 2、高阶组件(HOC)
      • 注意:这两种方式不是新的API,而是利用React自身特点的编码技巧,演化而成的固定模式(写法)
    • render props模式

      • 思路分析:

        • 思路:将要复用的state和操作state的方法封装到一个组件中
        • 问题1:如何拿到该组件中复用的state?
        • 在使用组件时,添加一个值为函数的prop,通过函数参数来获取(需要组件内部实现)
        • 问题2:如何渲染任意的UI?
        • 使用该函数的返回值作为要渲染的UI内容(需要组件内部实现)
      • 使用步骤:

        • 1、创建Mouse组件,在组件中提供复用的状态逻辑代码(1、状态 2、操作状态的方法)

        • 2、将要复用的状态作为props.render(state)方法的参数,暴露到组件外部

        • 3、使用props.render()的返回值作为要渲染的内容

        • 示例:

          class Mouse extends React.Component {
          
              // 鼠标位置
              state = {
                  x: 0,
                  y: 0
              }
          
              // 鼠标移动事件的事件处理程序
              handleMouseMove = e => {
                  this.setState({
                      x: e.clientX,
                      y: e.clientY
                  })
              }
          
              // 监听鼠标移动事件
              componentDidMount(){
                  window.addEventListener('mousemove', this.handleMouseMove)
              }
          
              render(){
                  // return null
                  return this.props.render(this.state)
              }
          
          }
          
          
          class App extends React.Component {
          
              render(){
                  return (
                      <div>
                          render props 模式
                          <Mouse render={(mouse) => (<p>鼠标的位置: x:{mouse.x}   y:{mouse.y}</p>)} />
                      </div>
                  )
              }
          }
          
          ReactDOM.render(<App/>, document.getElementById('root'))
          
      • 演示Mouse组件的复用

        • Mouse组件负责:封装复用的状态逻辑代码(1、状态 2、操作状态)

        • 状态:鼠标坐标(x, y)

        • 操作状态的方法:鼠标移动事件

        • 传入的render prop负责:使用复用的状态来渲染UI结构

        • 示例:

          import img from "./images/logo192.png"
          class Mouse extends React.Component {
          
              // 鼠标位置
              state = {
                  x: 0,
                  y: 0
              }
          
              // 鼠标移动事件的事件处理程序
              handleMouseMove = e => {
                  this.setState({
                      x: e.clientX,
                      y: e.clientY
                  })
              }
          
              // 监听鼠标移动事件
              componentDidMount(){
                  window.addEventListener('mousemove', this.handleMouseMove)
              }
          
              render(){
                  // return null
                  return this.props.render(this.state)
              }
          
          }
          
          
          class App extends React.Component {
          
              render(){
                  return (
                      <div>
                          render props 模式
                          <Mouse render={mouse => (<p>鼠标的位置: x:{mouse.x}   y:{mouse.y}</p>)} />
          
                          <Mouse render={mouse => (<img src={img} alt="logo" style={{
                              position: 'absolute',
                              top: mouse.y - 96,
                              left: mouse.x - 96
                          }}/>)} />
                      </div>
                  )
              }
          }
          
          ReactDOM.render(<App/>, document.getElementById('root'))
          
  • 相关阅读:
    操作系统复习
    你不知道的JS(2)深入了解闭包
    剑指offer(66)机器人的运动范围
    剑指offer(65)矩阵中的路径
    剑指offer(64)滑动窗口中的最大值
    剑指offer(63)数据流中的中位数
    剑指offer(62)二叉搜索树的第K个节点
    剑指offer(61)序列化二叉树
    剑指offer(60)把二叉树打印成多行
    让 Laravel API 永远返回 JSON 格式响应!
  • 原文地址:https://www.cnblogs.com/ghh520/p/15088283.html
Copyright © 2020-2023  润新知