前端基础能力 - React组件 - 图1

前言

React 是 Facebook 公司开发的用于构建用户界面的 JavaScript 库,React 是现在使用最广泛的组件库,相信大家都比较了解 React 的基本原理及使用,这里不再累赘,下面主要和大家一起学习下 React 新的特性。
翻阅官方文档是最好的学习途径,官方文档地址:

https://react.docschina.org/docs/getting-started.html

React 项目地址(附新特性源码)

https://github.com/dkypooh/front-end-develop-demo/tree/master/base/eslint-react

React 基础

React 生命周期

前端基础能力 - React组件 - 图2
从 React 生命周期中可以看出 shouldComponentUpdate 这个节点的生命周期关系到是否重新 Render 组件。对 shouldComponentUpdate 的手动触发也是组件性能优化的一个重要手段。

React 基础实例

编写一个 hello world 组件,JSX 语法编译成 React.createElement 包装的函数。

  1. import React from "react";
  2. export default class extends React.PureComponent {
  3. render() {
  4. return (
  5. <div className="App">
  6. <h1>Hello boy</h1>
  7. <h2>Welcome</h2>
  8. </div>
  9. );
  10. }
  11. }
  12. ## 编译后的结果
  13. return React.createElement('div', {className: 'App'},
  14. React.createElement('h1', /* ... h1 children ... */),
  15. React.createElement('h2', /* ... h2 children ... */)
  16. );

同时在当前版本的 React 中,支持编写函数式组件,官方称为 Function Component。通过 Webpack 的 Babel @babel/preset-react 支持编译成 Javascript 语法。

Portals 传送门

Portals 提供了一种很好的将子节点渲染到父组件以外的 DOM 节点的方式。

  1. ReactDOM.createPortal(child, container)

第一个参数( child )是任何可渲染的 React 子元素,例如一个元素,字符串或碎片。第二个参数(container)则是一个 DOM 元素。

  1. render() {
  2. return ReactDOM.createPortal(
  3. <Component />,
  4. domNode,
  5. );
  6. }

通常讲,当你从组件的 render 方法返回一个元素,该元素仅能装配 DOM 节点中离其最近的父元素。在 React@^16 中的这个特性可以动态插入到任何 DOM 节点的位置。 这种方式在编写 Modal, Dialog, Toast 等全局节点组件中很有用。
在 CodePen 上试一试

Context 上下文

React@v16.3.0 版本中引入 Context 的 API, Context 通过组件树提供了一个传递数据的方法,从而避免了在每一个层级手动的传递 Props 属性。通过 Context 数据共享的方式,保证组件数据不需要使用 Props 属性,实现跨级传输。 Context 详细API参考文档
Context 设计目的是为共享那些被认为对于一个组件树而言是“全局”的数据。下面例子来说明 PropsContext 数据通信的区别。

使用 Props 实例

  1. class App extends React.Component {
  2. render() {
  3. return <Toolbar theme="dark" />;
  4. }
  5. }
  6. function Toolbar(props) {
  7. return (
  8. <div>
  9. <ThemedButton theme={props.theme} />
  10. </div>
  11. );
  12. }
  13. class ThemedButton extends React.Component {
  14. render() {
  15. return <Button theme={this.props.theme} />;
  16. }
  17. }

使用 Context 实例

React.createContext 方法创建 Context 实例,创建了一对 { Provider, Consumer }。 当 React 渲染 Context 组件 Consumer 时,它将从组件树的上层中最接近的匹配的 Provider 读取当前的 context 值。

  1. // 创建 Context 实例
  2. const ThemeContext = React.createContext('light');
  3. class App extends React.Component {
  4. render() {
  5. return (
  6. // 提供 `Provider` 上下文容器
  7. <ThemeContext.Provider value="dark">
  8. <Toolbar />
  9. </ThemeContext.Provider>
  10. );
  11. }
  12. }
  13. // 返回 Toobar 组件函数
  14. function Toolbar(props) {
  15. return (
  16. <div>
  17. <ThemedButton />
  18. </div>
  19. );
  20. }
  21. // 构建组件实例
  22. class ThemedButton extends React.Component {d
  23. static contextType = ThemeContext;
  24. render() {
  25. return <Button theme={this.context} />;
  26. }
  27. }

React Hooks

  1. With React 16.8, React Hooks are available in a stable release! - February 06, 2019

现在 React Hooks 已经提案到了最终的标准, 读者之前开发过 React 相关的项目,都会遇到如下问题:

  1. It’s hard to reuse stateful logic between components
    不同组件之间的状态很难复用。
  2. Complex components become hard to understand 复杂组件导致很难理解
  3. Classes confuse both people and machines 大量的类定义导致很难维护。
  4. hooks let you use more of React’s features without classes.
    React Hooks 开发组件也变得越来越轻量级。
    技术的发展路径总是逐步降低其门槛,简单从轻量级角度我们做一个排序:
  1. createClass Component > Class Component > Function Component

React Hooks 动机也是为了解决 React 项目开发与维护的成本,Hooks 和 Function Compenent 配合让开发者不需要定义类,更加轻量级。
如下实例:

源码地址:https://github.com/dkypooh/front-end-develop-demo/tree/master/base/react-feature

  1. import React, { useState } from 'react';
  2. function App() {
  3. // Declare a new state variable, which we'll call "count"
  4. const [count, setCount] = useState(0);
  5. return (
  6. <div class="App">
  7. <p>You clicked {count} times</p>
  8. <button onClick={() => setCount(count + 1)}>
  9. Click me
  10. </button>
  11. </div>
  12. );
  13. }
  14. export default App;

Hooks 有几个关键 API: useStateuseEffectuseContext
useContext。 下面带大家一起了解下这些API的使用,以及他们解决的问题。

State Hooks

State Hooks API 为了解决组件内部状态难复用的问题,以轻量级隔离的方式创建状态和更改状态。

  1. const [state, setState] = useState(InitialState)

state 为状态名称, setState 为更改状态函数(当然可以去其他的,例如上文 setCount), InitialState 状态初始值。
也可以使用多个状态变量,这样的关系中维护写法做到了去中心化。

  1. ## 之前 state 语法
  2. this.state = {
  3. fruit: 'banana',
  4. age: 42,
  5. todos: 'Lerna Hooks'
  6. };
  7. this.setState({fruit: 'orange'});
  8. ## 现在 useState 语法
  9. const [age, setAge] = useState(42);
  10. const [fruit, setFruit] = useState('banana');
  11. const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
  12. setFruit('orange');

Effect Hooks

Effect Hooks 副作用钩子 API 为了链接组件生命周期,让组件更加简单。 Effect Hooks 可以同时在 componentDidMount 和 componentDidUpdate 上触发,同时返回会在 componentWillUnmount 的时候触发。 可以使用它代替一些生命周期,使书写更加简洁。
关于生命周期,使用 useEffect 基本解决了在 Fuction Component 无生命周期的问题

不使用 Effect

  1. import React from 'react';
  2. class NoEffect extends React.PureComponent {
  3. constructor(props) {
  4. super(props);
  5. this.state = {
  6. count: 0
  7. };
  8. }
  9. componentDidMount() {
  10. document.title = `You clicked ${this.state.count} times`;
  11. }
  12. componentDidUpdate() {
  13. document.title = `You clicked ${this.state.count} times`;
  14. }
  15. render() {
  16. return (
  17. <div>
  18. <p>You clicked {this.state.count} times</p>
  19. <button onClick={() => this.setState({ count: this.state.count + 1 })}>
  20. Click me
  21. </button>
  22. </div>
  23. );
  24. }
  25. }
  26. export default NoEffect;

使用 Effect

  1. import React, { useState, useEffect } from 'react';
  2. function UseEffect() {
  3. const [count, setCount] = useState(0);
  4. useEffect(() => {
  5. document.title = `You clicked ${count} times`;
  6. });
  7. return (
  8. <div>
  9. <p>You clicked {count} times</p>
  10. <button onClick={() => setCount(count + 1)}>
  11. Click me
  12. </button>
  13. </div>
  14. );
  15. }
  16. export default UseEffect;

Reducer Hooks

useReducer Hooks 可以模拟 Redux 的 reducer 行为,进行数据流转。 下面语法返回 statedispatch 两个属性

  1. const [state, dispatch] = useReducer(reducer, initialArg, init);

使用 useReducers

  1. // 初始化状态
  2. const initialState = {count: 0};
  3. // reducer规则
  4. function reducer(state, action) {
  5. switch (action.type) {
  6. case 'increment':
  7. return {count: state.count + 1};
  8. case 'decrement':
  9. return {count: state.count - 1};
  10. default:
  11. throw new Error();
  12. }
  13. }
  14. function Counter({initialCount}) {
  15. const [state, dispatch] = useReducer(reducer, initialState);
  16. return (
  17. <>
  18. Count: {state.count}
  19. <button onClick={() => dispatch({type: 'increment'})}>+</button>
  20. <button onClick={() => dispatch({type: 'decrement'})}>-</button>
  21. </>
  22. );
  23. }

useState 实现 useReducer

useState API 原理公式是:

  1. (state, action) => newState

useReducer 返回 dispatch 方法来触发状态的改变。 如下有通过 useState 实现案例。
useReducer源码实现:

https://github.com/facebook/react/blob/master/packages/react-reconciler/src/ReactFiberHooks.js#L543

  1. function useReducer(reducer, initialState) {
  2. const [state, setState] = useState(initialState);
  3. function dispatch(action) {
  4. const nextState = reducer(state, action);
  5. setState(nextState);
  6. }
  7. return [state, dispatch];
  8. }

React的高阶组件

HOC(Higher Order Component,高阶组件),它不是 React 的组件,而是一种设计模式。
理解:高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件, 同时函数执行创建了闭包环境。
计算公式如下:

  1. const EnhancedComponent = higherOrderComponent(WrappedComponent);

HOC 组件可以封装组件中通用的组件,可变的组件可以通过传入子组件的方式,提高代码复用性。

高阶组件实例

  1. function HOC(WrappedComponent) {
  2. return class extends React.Component {
  3. render() {
  4. return <WrappedComponent {...this.props}/>
  5. }
  6. }
  7. }

注释:这里的 HOC 是一个方法,接受一个 WrappedComponent 作为方法的参数,返回一个匿名 class,renderWrappedComponent。

结语

本章和大家一起来学习下 React 中的两个重要特性( Portals 和 Context ) 和 一个核心概念 ( Hooks ),通过本章学习读者可以知道为什么需要 Hooks , 以及它的适用场景,最后和大家一起编写一个消息流 React HOC 组件。
下一章将要学习 Mobx 状态管理, 大家需要提前了解下 Mobx基本概念, 同时最好了解下 Redux 的原理,下一章会思考 Mobx 和 Redux 的差异和不同的使用场景。
参考文档:

  1. Mobx 中文官网
  2. Redux 官网

思考题

Q: 编写一个 HOC 组件之消息流 HOC 组件,UI 如下,传入不同的 wrapper 组件,展示不同消息项。
前端基础能力 - React组件 - 图3

参考文档