react 进阶

懒加载

React.lazy函数能让你像渲染常规组件一样处理动态引入(的组件)。
Suspense加载指示器为组件做优雅降级。
fallback属性接受任何在组件加载过程中你想展示的 React 元素。

const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <OtherComponent />
      </Suspense>
    </div>
  );
}

Context

Context提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法,设计目的是为了共享那些对于一个组件树而言是“全局”的数据。

// Context 可以让我们无须明确地传遍每一个组件,就能将值深入传递进组件树。
// 为当前的 theme 创建一个 context(“light”为默认值)。
const ThemeContext = React.createContext('light');

class App extends React.Component {
  render() {
    // 使用一个 Provider 来将当前的 theme 传递给以下的组件树。
    // 无论多深,任何组件都能读取这个值。
    // 在这个例子中,我们将 “dark” 作为当前的值传递下去。
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

// 中间的组件再也不必指明往下传递 theme 了。
function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

class ThemedButton extends React.Component {
  // 指定 contextType 读取当前的 theme context。
  // React 会往上找到最近的 theme Provider,然后使用它的值。
  // 在这个例子中,当前的 theme 值为 “dark”。
  static contextType = ThemeContext;
  render() {
    return <Button theme={this.context} />;
  }
}

请谨慎使用,因为这会使得组件的复用性变差。

API:

React.createContext
创建一个 Context 对象。当 React 渲染一个订阅了这个 Context 对象的组件,这个组件会从组件树中离自身最近的那个匹配的 Provider 中读取到当前的 context 值。只有当组件所处的树中没有匹配到 Provider 时,其 defaultValue 参数才会生效。

Context.Provider
每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化。当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染。

Class.contextType
挂载在 class 上的 contextType 属性会被重赋值为一个由 React.createContext() 创建的 Context 对象。这能让你使用 this.context 来消费最近 Context 上的那个值。你可以在任何生命周期中访问到它,包括 render 函数中。

Context.Consumer
这里,React 组件也可以订阅到 context 变更。这能让你在函数式组件中完成订阅 context。

Refs

Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素。不能在函数组件上使用 ref 属性,因为他们没有实例,可以在函数组件内部使用 ref 属性。

适合使用 refs 的情况:

  • 管理焦点,文本选择或媒体播放。
  • 触发强制动画。
  • 集成第三方 DOM 库。

使用方法:

  1. 创建 Refs

    • Refs 是使用 React.createRef() 创建的,并通过 ref 属性附加到 React 元素。
class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }
  render() {
    return <div ref={this.myRef} />;
  }
}
  1. 访问 Refs

    • 在 ref 的 current 属性中被访问
const node = this.myRef.current;

Refs 转发

Ref 转发是一项将 ref 自动地通过组件传递到其一子组件的技巧。子组件使用React.forwardRef接收ref。可用于Hoc处理ref。

const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} className="FancyButton">
    {props.children}
  </button>
));

// 你可以直接获取 DOM button 的 ref:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;

上例中,FancyButton 使用 React.forwardRef 来获取传递给它的 ref,然后转发到它渲染的 DOM button。这样,使用 FancyButton 的组件可以获取底层 DOM 节点 buttonref ,并在必要时访问,就像其直接使用 DOM button 一样。

Fragments

Fragments 允许你将子列表分组,而无需向 DOM 添加额外节点。key 是唯一可以传递给 Fragment 的属性

render() {
  return (
    <React.Fragment>
      <ChildA />
      <ChildB />
      <ChildC />
    </React.Fragment>
  );
}

<React.Fragment></React.Fragment>可简写为<></>

高阶组件(HOC)

HOC是参数为组件,返回值为新组件的函数。

HOC 不会修改传入的组件,也不会使用继承来复制其行为。相反,HOC 通过将组件包装在容器组件中来组成新组件。HOC 是纯函数,没有副作用。

示例:

// 此函数接收一个组件...
function withSubscription(WrappedComponent, selectData) {
  // ...并返回另一个组件...
  return class extends React.Component {
    constructor(props) {
      super(props);
      this.handleChange = this.handleChange.bind(this);
      this.state = {
        data: selectData(DataSource, props)
      };
    }

    componentDidMount() {
      // ...负责订阅相关的操作...
      DataSource.addChangeListener(this.handleChange);
    }

    componentWillUnmount() {
      DataSource.removeChangeListener(this.handleChange);
    }

    handleChange() {
      this.setState({
        data: selectData(DataSource, this.props)
      });
    }

    render() {
      // ... 并使用新数据渲染被包装的组件!
      // 请注意,我们可能还会传递其他属性
      return <WrappedComponent data={this.state.data} {...this.props} />;
    }
  };
}

上例中class组件为HOC的容器组件,容器组件担任分离将高层和低层关注的责任,由容器管理订阅和状态,并将 prop 传递给处理渲染 UI。HOC 使用容器作为其实现的一部分,你可以将 HOC 视为参数化容器组件。

注意事项:

  • 不要在 render 方法中使用 HOC。

    在render中使用会导致diff 算法在对比组件变化时每次检测都不一样,每次渲染都会进行卸载,和重新挂载的操作,这不仅仅是性能问题 - 重新挂载组件会导致该组件及其所有子组件的状态丢失。

  • 务必复制静态方法到容器组件上。

    可以使用hoist-non-react-statics自动拷贝所有非 React 静态方法

    import hoistNonReactStatic from 'hoist-non-react-statics';
    function enhance(WrappedComponent) {
      class Enhance extends React.Component {/*...*/}
      hoistNonReactStatic(Enhance, WrappedComponent);
      return Enhance;
    }
  • Refs 不会被传递。

    可用过Refs 转发解决

常见的HOC:

redux的 connect

React.PureComponent

大部分情况下,你可以使用 React.PureComponent 来代替手写 shouldComponentUpdate。只有当检测数据是数组或对象时,由于浅拷贝的问题会导致比较出现偏差不能使用,此时使用深拷贝仍可继续使用。

如以下代码:

class CounterButton extends React.Component {
  constructor(props) {
    super(props);
    this.state = {count: 1};
  }

  shouldComponentUpdate(nextProps, nextState) {
    if (this.props.color !== nextProps.color) {
      return true;
    }
    if (this.state.count !== nextState.count) {
      return true;
    }
    return false;
  }

  render() {
    return (
      <button
        color={this.props.color}
        onClick={() => this.setState(state => ({count: state.count + 1}))}>
        Count: {this.state.count}
      </button>
    );
  }
}

可替换为:

class CounterButton extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {count: 1};
  }

  render() {
    return (
      <button
        color={this.props.color}
        onClick={() => this.setState(state => ({count: state.count + 1}))}>
        Count: {this.state.count}
      </button>
    );
  }
}

Portals

Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案。

一个 portal 的典型用例是当父组件有 overflow: hidden 或 z-index 样式时,但你需要子组件能够在视觉上“跳出”其容器。例如,对话框、悬浮卡以及提示框:

render() {
  // React 并*没有*创建一个新的 div。它只是把子元素渲染到 `domNode` 中。
  // `domNode` 是一个可以在任何位置的有效 DOM 节点。
  return ReactDOM.createPortal(
    this.props.children,
    domNode
  );
}

React.StrictMode

StrictMode 不会渲染任何可见的 UI。它为其后代元素触发额外的检查和警告。严格模式检查仅在开发模式下运行;它们不会影响生产构建。

作用:
  • 识别不安全的生命周期
  • 关于使用过时字符串 ref API 的警告
  • 关于使用废弃的 findDOMNode 方法的警告
  • 检测意外的副作用
  • 检测过时的 context API

React.memo

React.memo 为高阶组件。它与 React.PureComponent 非常相似,但它适用于函数组件,但不适用于 class 组件。

如果你的函数组件在给定相同 props 的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo 中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。

默认情况下其只会对复杂对象做浅层对比,如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现。

const MyComponent = React.memo(function MyComponent(props) {
  /* 使用 props 渲染 */
});

点击获取更多知识点 觉得有用的话,来个star鼓励,持续更新中。


tigerHee
103 声望1 粉丝

页面仔一枚