react组件通信

2,169 阅读5分钟

1 父子通信

  • 在父组件中的子组件标签上将父组件的变量,方法通过属性的形式让子组件可以访问

  • 在子组件中通过this.props.属性名访问对应变量或方法

  • 方法应定义为箭头函数,避免不同组件调用方法导致this指向混乱

    // 以todos功能为例
    
    // 父组件
    import React, { Component } from 'react'
    import AddItem from './AddItem'
    import Todolist from './Todolist'
    
    class Content extends Component {
      constructor(props) {
        super(props)
        this.state = {//父组件中的变量
          value: '',
          arrList: []
        }
      }
      // 定义方法
      // 用setState函数修改state中变量,避免直接对state中的变量进行操作
      // 为了子组件调用父组件方法时不改变this指向,应将方法定义为箭头函数
      handleChange = (value) => {
        this.setState({//异步操作,但若在定时器中使用就是同步操作
          value
        })
      }
      handleClick = () => {
        this.setState({
          arrList: [...this.state.arrList, this.state.value],
          value: ''
        })
      }
      deleteClick = (id) => {
        let temparr = this.state.arrList
        temparr.splice(id,1)
        this.setState({
          arrList: temparr
        })
      }
      render() {
        return (
          <div>
            <AddItem
              value = {this.state.value}
              handleChange = {this.handleChange}
              handleClick = {this.handleClick}
            />
            <Todolist
              arrList = {this.state.arrList}
              deleteClick = {this.deleteClick}
            />
          </div>
        )
      }
    }
    
    export default Content
    
    // 子组件 AddItem
    import React, { Component, Fragment } from 'react'
    
    class AddItem extends Component {
      getInpValue = (e) => {
        this.props.handleChange(e.target.value)
      }
      render() {
        return (
          <Fragment>
            <input type = "text" value = {this.props.value} onChange = {this.getInpValue} />
            <button onClick = {this.props.handleClick}>添加任务</button>
          </Fragment>
        )
      }
    }
    
    export default AddItem
    
    // 子组件 Todolist
    import React, { Component } from 'react'
    
    class Todolist extends Component {
      // 使用父组件方法需要传参,应在子组件方法中调用,通过bind绑定参数给子组件方法传参
      // 虽然父组件定义方法时箭头函数时,子组件给该方法bind绑定参数也可以,但以上方法更可靠
      deleteItem (i) {
        this.props.deleteClick(i)
      }
      render() {
        return (
          <ul>
            {
              this.props.arrList.map((item,index) => {
                return (
                  <li key = {index}>
                    {item}
                    <span onClick = {this.props.deleteClick.bind(this,index)}>X</span>
                  </li>
                )
              })
            }
          </ul>
        )
      }
    }
    
    export default Todolist
    

2 利用context跨组件通信

  • context的功能

    • 在react没有类似vue中的事件总线来解决这个问题
    • 我们只能借助它们共同的父级组件来实现,将非父子关系装换成多维度的父子关系
    • react提供了context api来实现跨组件通信, React 16.3之后的contextapi较之前的好用
  • 使用方法

    • React.createContext() 我们需要用到createContext创建上下文

    • 返回的是一个对象 对象内有2个组件

      • Provider:设置共享状态及方法
        • 通过value去传递
      • Consumer:接收共享状态或方法
        • 只要被 Provider这个组件包裹的所有组件都可以通过Consumer接收
        • Consumer 组件 内部必须返回一个函数,这个函数会接受到 Provider传递过来的状态及方法
    • 示例:按钮实现加/减

      • index.js
      // index.js
      import React from 'react';
      import ReactDOM from 'react-dom';
      import App from './App';
      import CountProvider from './context'
      // 组件标签内就算有内容也不会自动去解析,需在组件内部通过this.props.children获取使用
      ReactDOM.render(
        (
          <CountProvider>
            <App />
          </CountProvider>
        ),
        document.getElementById('root'));
      
      • context.js

        import React, { createContext, Component, Fragment } from 'react'
        // Fragment不会生成DOM标签,一般用来做根标签,也可以用<></>代替
        const {Consumer, Provider} = createContext()
        
        class CountProvider extends Component {
          constructor(props) {
            super(props)
            this.state = {
              num: 10
            }
          }
          addNum = () => {
            this.setState({
              num: this.state.num + 1
            })
          }
          minusNum = () => {
            this.setState({
              num: this.state.num - 1
            })
          }
          // 此处this.props.children获取到的是APP组件,故在App组件中使用的组件都可以可以通过Consumer接收
          render() {
            return (
              <Fragment>
                <Provider value = {{
                  num: this.state.num,
                  addNum: this.addNum,
                  minusNum: this.minusNum
                }}>
                  {this.props.children}
                </Provider>
              </Fragment>
            )
          }
        }
        // export 导出语法有
        	// export let/const 变量名
        	// 或export function 函数名 () {}
        	// 或export {变量名}
        // export default 可直接导出 变量,但只能使用一次
        export default CountProvider
        export {Consumer, Provider}
        
      • App.js

        import React, { Component } from 'react'
        import CountButton from './components/button'
        import Count from './components/count'
        
        class App extends Component {
          render () {
            return (
              <div>
                <CountButton type='add'>+</CountButton>
                <Count />
                <CountButton type='minus'>-</CountButton>
              </div>
            )
          }
        }
        
        export default App;
        
      • /count/index.js

        import React, { Component } from "react";
        import {Consumer} from '../../context'
        
        class Count extends Component {
          render() {
            return (
              <Consumer>
                {
                  ({num}) => {
                    return (
                      <span>{num}</span>
                    )
                  }
                }
              </Consumer>
            )
          }
        }
        
        export default Count
        
      • /button/index.js

        import React, { Component } from 'react'
        import { Consumer } from '../../context'
        
        class CountButton extends Component {
          render() {
            return (
              <Consumer>
                {
                  ({addNum, minusNum})=>{
                    const setFunc = this.props.type === 'add' ? addNum : minusNum
                    return (
                      <button onClick={setFunc}>
                        {this.props.children}
                      </button>
                    )
                  }
                }
              </Consumer>
            )
          }
        }
        
        export default CountButton
        

3 ref通信

  • 适用场景

    • 对DOM 元素焦点的控制、内容选择或者媒体播放
    • 通过对DOM元素控制,触发动画特效
    • 集成第三方 DOM 库
  • 避免使用

    • 避免使用 refs 去做任何可以通过声明式实现来完成的事情
    • 例如,避免在Dialog、Loading、Alert等组件内部暴露 open(), show(), hide(),close()等方法,最好通过 isXX属性的方式来控制
  • 使用限制

    • 函数组件内部不支持使用 字符串 refs (支持 createRef 、 useRef 、 回调 Ref)

    • 不能在函数组件上使用 ref 属性,因为函数组件没有实例

    • useRef 仅限于在函数组件内使用

3.1 String类型的refs

  • 在元素或组件标签上设置ref属性

    <button ref = 'btnRef' onClick = {this.handleClick}>dianwo</button>
    <Son ref = 'sonRef' />
    
  • 通过this.refs得到所有设置了ref属性的元素或组件实例

    • 如通过this.refs.sonRef可访问Son组件中的状态和方法
    • 通过this.refs.btnRef操作真实button DOM
  • 过时的API,官方建议使用 回调函数 或者 createRef API的方式来替换

  • 函数组件内部不支持使用

3.2 createRef API

  • 在React16.3 版本中引入的

  • 使用 React.createRef() 创建 Refs,并通过 ref 属性附加至 React 元素上。通常在构造函数中,将 Refs 分配给实例属性,以便在整个组件中引用。

  • ref 被传递给 render 中的元素时,对该节点的引用可以在 refcurrent 属性中访问

  • ref 的值根据节点的类型而有所不同

  • ref 属性用于 HTML 元素时,构造函数中使用 React.createRef() 创建的 ref 接收底层 DOM 元素作为其 current 属性

  • ref 属性用于自定义的 class 组件时, ref 对象接收组件的挂载实例作为其 current 属性

  • 不能在函数组件上使用 ref 属性,因为函数组件没有实例

this.sonRef = createRef()
<Son ref = {this.sonRef} />
this.sonRef.current

3.3 useRef API

  • useRef 是 React16.8 中引入的,只能在函数组件中使用

  • 使用 React.useRef() 创建 Refs,并通过 ref 属性附加至 React 元素上

  • 返回的 ref 对象在组件的整个生命周期内保持不变

  • ref 被传递给 React 元素时,对该节点的引用可以在 refcurrent 属性中访问

import React from 'react';

export default function MyInput(props) {
    const inputRef = React.useRef(null);
    React.useEffect(() => {
        inputRef.current.focus();
    });
    return (
        <input type="text" ref={inputRef} />
    )
}

3.4 回调形式的refs

  • 在react较早的版本中,我们推荐使用 回调形式的refs

  • 这种方式可以帮助我们更精细的控制何时 Refs 被设置和解除

  • 将回调函数传递给 React元素ref 属性。这个函数接受 React 组件实例 或 HTML DOM 元素作为参数,将其挂载到实例属性上

  • React 会在组件挂载时,调用 ref 回调函数并传入 DOM元素(或React实例),当卸载时调用它并传入 null。在 componentDidMountcomponentDidUpdate 触发前,React 会保证 Refs 一定是最新的

import React from 'react';

export default class MyInput extends React.Component {
    constructor(props) {
        super(props);
        this.inputRef = null;
        this.setTextInputRef = (ele) => {
            this.inputRef = ele;
        }
    }

    componentDidMount() {
        this.inputRef && this.inputRef.focus();
    }
    render() {
        return (
            <input type="text" ref={this.setTextInputRef}/>
        )
    }
}

参考: 你想知道的关于 Refs 的知识都在这了 ---刘小夕

基础决定未来,一步一个脚印