React16.2-16.6

React16.2-16.6功能总结。涉及生命周期变化、StrictMode、Profiler、全新的context api和refs相关改动。

生命周期变化

原因

在16使用的Fiber架构中, React组件渲染分为两个阶段。第一阶段是渲染阶段,这一阶段做的是Fiber的update,然后产出的是effect list(可以想象成将老的View更新到新的状态所需要做的DOM操作的列表)。这一个阶段是没有副作用的,因此这个过程可以被打断,然后恢复执行。第二阶段是提交阶段。渲染阶段产生的effect list只有在提交之后才会生效,也就是真正应用到DOM中。这一阶段往往不会执行太长时间,因此是同步的,这样也避免了组件内视图层结构和DOM不一致。这样就会导致render之前的生命周期在一次渲染中多次执行产生副作用。

Fiber源码解读 Fiber相关完全理解))

新生命周期图

lifeCircle

getDerivedStateFromProps

  static getDerivedStateFromProps(nextProps, prevState) {
    return {
      count: nextProps.count * 100,
    };
  }
  static getDerivedStateFromProps(nextProps, prevState) {
    if (nextProps.id !== prevState.prevId) {
      return {
        externalData: null,
        prevId: nextProps.id,
      };
    }
    // 不需要更新state
    return null;
  }
  componentDidUpdate(prevProps, prevState) {
    if (this.state.externalData === null) {
      // 请求
    }
  }

getSnapshotBeforeUpdate

getSnapshotBeforeUpdate(prevProps, prevState) {
  return 1;
}

由于componentWillUpdatecomponentDidUpdate执行的时间差,如果使用componentWillUpdate,会导致从componentWillUpdate中获取数据会不准确。

class ScrollingList extends React.Component {
  listRef = null;

  getSnapshotBeforeUpdate(prevProps, prevState) {
    // 是否在向列表中添加新条目?
    // 捕捉滚动位置,以便我们调整滚动条。
    if (prevProps.list.length < this.props.list.length) {
      return (
        this.listRef.scrollHeight - this.listRef.scrollTop
      );
    }
    return null;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    // 如果我们拥有了快照值,则说明刚刚添加了新条目。
    // 调整滚动条,以免使旧条目被新条目移出可视范围。
    // (这里的snapshot 指的是从getSnapshotBeforeUpdate 返回的值)
    if (snapshot !== null) {
      this.listRef.scrollTop =
        this.listRef.scrollHeight - snapshot;
    }
  }

  render() {
    return (
      <div ref={this.setListRef}>
        {/* ...contents... */}
      </div>
    );
  }

  setListRef = ref => {
    this.listRef = ref;
  };
}

StrictMode

Profiler

搜集性能数据

全新的context api

功能

共享那些被认为对于一个组件树而言是“全局”的数据,不必通过组件树的每个层级显式地传递 props 。

与老的context区别

基本用法

  // 创建 context 实例
  const ThemeContext = React.createContext('light');

  class App extends React.Component {
    render() {
      return (
        <ThemeContext.Provider value="dark">
          <Toolbar />
        </ThemeContext.Provider>
      );
    }
  }
  Toolbar(props) {
    return (
      <div>
        <ThemedButton />
      </div>
    );
  }

  ThemedButton(props) {
    return (
      <ThemeContext.Consumer>
        {theme => <Button {...props} theme={theme} />}
      </ThemeContext.Consumer>
    );
  }

Refs

新的refs创建方法

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }
  render() {
    return <div ref={this.myRef} />;
  }
}

转发refs

方法1: 为类组件添加 Ref,可以通过this.myRef.current.func 调用组件实例的方法来操作 dom。(不推荐此方法,1.你不能在函数式组件上使用 ref 属性,因为它们没有实例。 2.它会破坏组件的封装)

class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    // 创建 ref 存储 textInput DOM 元素
    this.textInput = React.createRef();
    this.focusTextInput = this.focusTextInput.bind(this);
  }

  focusTextInput() {
    // 直接使用原生 API 使 text 输入框获得焦点
    // 注意:通过 "current" 取得 DOM 节点
    this.textInput.current.focus();
  }

  render() {
    // 告诉 React 我们想把 <input> ref 关联到构造器里创建的 `textInput` 上
    return (
      <div>
        <input
          type="text"
          ref={this.textInput}
        />  
        <input
          type="button"
          value="Focus the text input"
          onClick={this.focusTextInput}
        />
      </div>
    );
  }
}

class AutoFocusTextInput extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = React.createRef();
  }

  componentDidMount() {
    this.textInput.current.focusTextInput();
  }

  render() {
    return (
      <CustomTextInput ref={this.textInput} />
    );
  }
}

方法2:使用React.forwardRef

// 高阶组件
import React from 'react';

function logProps(Component) {
  class LogProps extends React.Component {
    componentDidUpdate(prevProps) {
      console.log('先前的属性:', prevProps);
      console.log('当前属性:', this.props);
    }

    render() {
      // 使用 forwardedRef 作为一个 ref 属性传入组件中
      const { forwardedRef, ...rest } = this.props;
      return (
        <Component ref={forwardedRef} {...rest} />
      );
    }
  }
  
  // 使用React.forwardRef对LogProps组件进行转发
  return React.forwardRef((props, ref) => (
    {' 上面定义的LogProps组件接受一个forwarded属性 '}
    <LogProps forwardedRef={ref} {...props} />
  ));
}

// FancyButton.js 子组件
import React from 'react';
import logProps from './logProps';

// 接受 props 和 ref 作为参数
// 返回一个React 组件
const FancyButton = React.forwardRef((props, ref) => (
    <button class="fancybutton" ref={ref}>
    {props.children}
  </button>
));

// 使用高阶组件对其进行封装
export default logProps(FancyButton);

// 父组件
// app.js
class App extends React.Component {
  
  constructor(props) {
    super(props);
    // 创建一个ref 名字随意
    this.fancyButtonRef = React.createRef();
  }
  
  componentDidMount() {
    console.log('ref', this.ref);
    // this.ref.current 表示获取ref指向的DOM元素
    this.ref.current.classList.add('primary'); // 给FancyButton中的button添加一个class
    this.ref.current.focus(); // focus到button元素上
  }
  
  render() {
    // 直接使用ref={this.fancyButtonRef}
    return (
        <FancyButton ref={this.fancyButtonRef}>子组件</FancyButton>
    );
  }
}

END