组件间通讯及钩子函数

组件通讯介绍

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

组件的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'))
        
原文地址:https://www.cnblogs.com/ghh520/p/15088283.html