React 16.0-16.9日常使用的新功能介绍(详细指南)

241 阅读3分钟

这是为从React 15迁移到React 16,或从早期的16.x版本迁移到16.9的开发者准备的一份简短的小抄。它侧重于你会经常使用的功能。

从带有片段的组件中返回多个元素

将UI分割成小的可重用组件可能会导致创建不必要的DOM元素,比如当你需要从一个组件返回多个元素时。React 16有几个选项可以避免这种情况:

// React 15: extra wrapper element
const Breakfast = () => (
  <ul>
    <li>Coffee</li>
    <li>Croissant</li>
    <li>Marmalade</li>
  </ul>
);

// React 16.0: array (note that keys are required)
const Breakfast = () => [
  <li key="coffee">Coffee</li>,
  <li key="croissant">Croissant</li>,
  <li key="marmalade">Marmalade</li>
];

// React 16.2: fragment
const Breakfast = () => (
  <React.Fragment>
    <li>Coffee</li>
    <li>Croissant</li>
    <li>Marmalade</li>
  </React.Fragment>
);

// React 16.2: fragment (short syntax)
const Breakfast = () => (
  <>
    <li>Coffee</li>
    <li>Croissant</li>
    <li>Marmalade</li>
  </>
);

// React 16: fragments composition
const Meals = (
  <ul>
    <Breakfast />
    <Lunch />
    <Dinner />
  </ul>
);

注意 你所使用的工具可能不支持这种简短的语法。

从组件中返回字符串和数字

在React 16中,组件可以返回字符串和数字。这对那些不需要任何标记的组件很有用,比如国际化或格式化:

// React 15
const LocalDate = ({ date }) => (
  <span>
    {date.toLocaleDateString('de-DE', {
      year: 'numeric',
      month: 'long',
      day: 'numeric'
    })}
  </span>
);

// React 16
const LocalDate = ({ date }) =>
  date.toLocaleDateString('de-DE', {
    year: 'numeric',
    month: 'long',
    day: 'numeric'
  });

取消setState()以避免重新渲染

在React 15中,如果你的下一个状态是基于先前的状态,那么就不可能取消setState() ,避免重新渲染。在React 16中,你可以在setState()'s callback中返回null

// React 16
handleChange = event => {
  const city = event.target.value;
  this.setState(prevState =>
    prevState.city !== city ? { city } : null
  );
};

在这个例子中,用与状态中相同的城市名称调用handleChange() ,不会导致重新渲染。

使用官方上下文API避免道具钻取(16.3)

道具钻取是指你使用一个道具将一些数据传递给一个深度嵌套的组件,所以你必须在拥有数据的组件和消耗数据的组件之间的React组件树的每一层添加这个道具:

class Root extends React.Component {
  state = { theme: THEME_DARK };
  handleThemeToggle = theme =>
    this.setState(({ theme }) => ({
      theme: theme === THEME_DARK ? THEME_LIGHT : THEME_DARK;
    }));
  render() {
    return (
      <Page
        onThemeToggle={this.handleThemeToggle}
        {...this.state}
        {...this.props}
      />
    );
  }
}

// Each layer will have to pass theme and theme toggle handler props
<SomeOtherComponent
  onThemeToggle={props.onThemeToggle}
  theme={props.theme}
/>;

// Many layers below
const Header = ({ theme, onThemeToggle }) => (
  <header className={cx('header', `header--${theme}`)}>
    ...
    <button onClick={onThemeToggle}>Toggle theme</button>
  </header>
);

这可是一大堆模板代码啊!通过上下文API,我们可以在组件树的任何地方访问我们的主题道具:

const ThemeContext = React.createContext(THEME_DARK);

// We should wrap our app in this component
class ThemeProvider extends React.Component {
  state = { theme: THEME_DARK };
  handleThemeToggle = theme =>
    this.setState(({ theme }) => ({
      theme: theme === THEME_DARK ? THEME_LIGHT : THEME_DARK
    }));
  render() {
    return (
      <ThemeContext.Provider
        value={{
          onThemeToggle: this.handleThemeToggle,
          theme: this.state.theme
        }}
      >
        {this.props.children}
      </ThemeContext.Provider>
    );
  }
}

// And then use theme consumer anywhere in the component tree
const Header = () => (
  <ThemeContext.Consumer>
    {({ theme, onThemeToggle }) => (
      <header className={cx('header', `header--${theme}`)}>
        ...
        <button onClick={onThemeToggle}>Toggle theme</button>
      </header>
    )}
  </ThemeContext.Consumer>
);

用getDerivedStateFromProps()更新基于道具的状态(16.3)

getDerivedStateFromProps() 生命周期方法是对componentWillReceiveProps() 的一种替代。当你有一个带有默认值的状态属性的道具,但你想在该道具改变时重置状态时,它就很有用。例如,一个模态有一个道具说它是否最初打开,还有一个状态说模态是否现在打开:

// React 15
class Modal extends React.Component {
  state = {
    isOpen: this.props.isOpen
  };
  componentWillReceiveProps(nextProps) {
    if (nextProps.isOpen !== this.state.isOpen) {
      this.setState({
        isOpen: nextProps.isOpen
      });
    }
  }
}

// React 16.3
class Modal extends React.Component {
  state = {};
  static getDerivedStateFromProps(nextProps, prevState) {
    if (nextProps.isOpen !== prevState.isOpen) {
      return {
        isOpen: nextProps.isOpen
      };
    }
  }
}

getDerivedStateFromProps() 方法在一个组件被创建和接收新的道具时被调用,所以你不必两次将道具转换为状态(在初始化和componentWillReceiveProps )。

用React.memo()在道具变化时重新渲染功能组件(16.6)

React.memo()对函数组件的作用与PureComponent对类组件的作用相同:只有当组件的props发生变化时才会重新渲染:

const MyComponent = React.memo(props => {
  /* Only rerenders if props change */
});

用contextType更容易访问类组件中的上下文(16.6)

Class.contextType简化了类组件中对React上下文的访问:

class App extends React.Component {
  static contextType = ThemeContext;
  componentDidMount() {
    const { theme } = this.context;
    /* ... */
  }
  componentDidUpdate() {
    const { theme } = this.context;
    /* ... */
  }
  componentWillUnmount() {
    const { theme } = this.context;
    /* ... */
  }
  render() {
    const { theme } = this.context;
    return (
      <h1>
        {theme === THEME_DARK
          ? 'Welcome to the dark side!'
          : 'Welcome to the light side!'}
      </h1>
    );
  }
}

用钩子在功能组件中使用状态 (16.8)

钩子允许你在函数组件中使用状态和其他React特性。通常情况下,使用钩子的代码比使用类更简单。

例如,我们可以用useState钩子来使用状态:

function Counter() {
  // A counter with the default value of 0
  const [count, setCount] = React.useState(0);
  return (
    <>
      <p>You’ve eaten {count} croissants.</p>
      <button onClick={() => setCount(count + 1)}>
        Eat a croissant!
      </button>
    </>
  );
}