React应用数据传递的方式

1. props属性

典型的React应用,数据通过props按照自上而下(父->子)的顺序传递数据。

2. Context传值

1. 应用场景

对于一些应用中全局性的属性(如UI主题、语言、登陆用户等),通过props传递会很繁琐。

Context的出现可以在组件之间(父->子)共享这些属性。

2. 使用方法

1. 创建Context对象(写入一个单独的文件)

const ThemeContext = React.createContext(defaultValue)
const ThemeContext = React.createContext({
    theme: themes.dark,
    changeTheme: () => {}
});

2. 使用Provider提供数据,数据可以是原始类型,也可以是对象;

可以是常量也可以是变量;

注意: 如果是对象,通过state值传入,否则每次渲染都是新的对象。

      <ThemeContext.Provider value={this.state}>
        <Toolbar />
      </ThemeContext.Provider>

3. 使用Consumer获取数据

      <ThemeContext.Consumer>
        {({theme, changeTheme}) => <div style={{background: theme.backgroundColor}}>
          <button onClick={changeTheme}>切换主题</button>
        </div>}       
      </ThemeContext.Consumer>

4. 也可以通过赋值静态属性contextType后,通过this.context获取值

  static contextType = ThemeContext;
  render() {
    return (
      <div style={{background: this.context.theme.backgroundColor}}>
        <button onClick={this.context.changeTheme}>切换主题</button>
      </div>

3. createRef API生成的Refs(React V16.3+)

将ref自动的通过组件传递到子组件。然后通过ref获取子组件的DOM。

1. Refs应用场景

1. 对于那些高可复用的,诸如<Button><SelectInput>等“叶”组件,经常会需要获取

DOM节点,实现管理焦点、文本选择、播放动画等。

2. 对于触发强制动画。

3.对于集成第三方DOM库。

注意: 高级组件不能通过属性转发ref

2. 访问ref的current属性

1. 对于HTML元素,current属性指向底层的DOM元素。

2.对于class组件元素,current属性指向组件的实例对象(即this)。

这样可以在父组件中,通过this.ref.current调用子组件的实例方法

class App extends React.Component {
   constructor(props) {
      super(props);
      this.ref = React.createRef();
   }
   componnetDidMount() {
      // this.ref.current指向子组件的实例对象this
      this.ref.current.resetData()
   }
   render() {
      // 只能是类子组件
      return <Child ref={this.ref}>
   }
}

class Child extends React.Component {
   resetData = () => {
      // TODO
   }
   render() {
      return <div></div>
   }
}

3. 不能在函数组件上使用ref,因为它没有实例。

function FunComponent(props) {
  return <div></div>
}
// 错误!函数组件上不能使用ref属性
<FunComponent ref={this.myref}/>

但是可以在函数组件内部使用,只要它指向DOM或者类组件。

function CustomTextInput(props) {
  // 这里必须声明 textInput,这样 ref 才可以引用它
  let textInput = React.createRef();
  function handleClick() {
    textInput.current.focus();
  }
  return (
    <div>
      <input
        type="text"
        ref={textInput} />
      <input
        type="button"
        value="Focus the text input"
        onClick={handleClick}
      />
    </div>
  );
}

3. Refs转发方法

1. 非高阶组件转发

class App extends React.Component{
  constructor(props) {
    super(props);
    this.ref = React.createRef(); //初始化一个变量
  }
  componentDidMount() {
    console.log(this.ref.current); // <div >Toolbar</div>
  }
  render() {
    return (
      <div>
        <Toolbar ref={this.ref} />
        <div>App</div>        
      </div>
    )
  }
}
// 传递到子组件后给其赋值
const Toolbar = React.forwardRef((props, ref) => (
  <div ref={ref} >
    Toolbar
  </div>
));

2. 高阶组件转发refs

当使用{...this.props}进行透传时,因为ref不是属性,所以不能通过其透传。

但是可以在高阶组件内部,通过React.forwardRef()进行转发。

// 高阶组件代码
function logProps(WrappedComponent) {
  class LogProps extends React.Component {
    componentDidUpdate(prevProps, prevState, snapshot) {
      console.log('prevProps-->',prevProps)
      console.log('this.props-->',this.props)
    }
    render() {
      const {forwardRef, ...rest} = this.props;
      // ref指向被包裹的组件
      return <WrappedComponent {...rest} ref={forwardRef} />
    }
  }
  return React.forwardRef((props, ref) => {
    return <LogProps {...props} forwardRef={ref} />
  });
} 
export default logProps;

// 被包裹组件代码
import React from 'react';
import logProps from './logProps';
class Toolbar extends React.Component {
  render() {
    return (
      <div>Toolbar</div>
    )
  }
}
export default logProps(Toolbar)

// 父组件代码
import Toolbar from './toolbar';

class App extends React.Component{
  constructor(props) {
    super(props);
    this.ref = React.createRef();
    this.state = {num: 1}
  }
  componentDidMount() {
    console.log(this.ref.current); //Toolbar
  }
  add = () => {
    this.setState(state => ({
      num: state.num + 1
    }))
  }
  render() {
    return (
      <div>
        <Toolbar ref={this.ref} />
        <div onClick={this.add}>{this.state.num}</div>        
      </div>
    )
  }
}

4. 回调函数Refs

ref还可以接收一个回调函数,作为ref的属性内容。

<div ref={(element) => this.ref = element}></div>

使用回调函数也ref可以获取子组件的DOM节点。

function FunComponent(props) {
  return <div ref={props.funRef}>函数组件</div>
}
class App extends React.Component {
  componentDidMount() {
    // 获取到函数子组件的DOM节点
    console.log(this.ref); //<div>函数组件</div>
  }
  render() {
    return(
      <FunComponent funRef={(element) => {this.ref = element}} />
    )
  }
}