react新知识-2020

288 阅读7分钟

高级知识

组件懒加载

React.lazy 接受一个函数,这个函数需要动态调用 import()。它必须返回一个 Promise,该 Promise 需要 resolve 一个 default export 的 React 组件。

Suspense 组件中渲染 lazy 组件,如此使得我们可以使用在等待加载 lazy 组件时做优雅降级(如 loading 指示器等)。

可以将 Suspense 组件置于懒加载组件之上的任何位置。你甚至可以用一个 Suspense 组件包裹多个懒加载组件。

import React, { Suspense } from 'react';

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

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

////和路由库一起使用
const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));
const App = () => (
  <Router>
    <Suspense fallback={<div>Loading...</div>}>
      <Switch>
        <Route exact path="/" component={Home}/>
        <Route path="/about" component={About}/>
      </Switch>
    </Suspense>
  </Router>
);

////命名导出
// ManyComponents.js
export const MyComponent = /* ... */;
export const MyUnusedComponent = /* ... */;
// MyComponent.js 中间模块重新导出默认模块
export { MyComponent as default } from "./ManyComponents.js";
const MyComponent = lazy(() => import("./MyComponent.js"));

错误边界

错误边界是一种 React 组件,这种组件可以捕获并打印发生在其子组件树任何位置的 JavaScript 错误,并且,它会渲染出备用 UI,而不是渲染那些崩溃了的子组件树。错误边界在渲染期间、生命周期方法和整个组件树的构造函数中捕获错误。

错误边界无法捕获以下场景中产生的错误:

  • 事件处理(了解更多)=》使用普通的 JavaScript try / catch 语句
  • 异步代码(例如 setTimeoutrequestAnimationFrame 回调函数)=》使用普通的 JavaScript try / catch 语句
  • 服务端渲染
  • 它自身抛出来的错误(并非它的子组件)

如果一个 class 组件中定义了 static getDerivedStateFromError()componentDidCatch() 这两个生命周期方法中的任意一个(或两个)时,那么它就变成一个错误边界。当抛出错误后,请使用 static getDerivedStateFromError() 渲染备用 UI ,使用 componentDidCatch() 打印错误信息

自 React 16 起,任何未被错误边界捕获的错误将会导致整个 React 组件树被卸载。

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // 更新 state 使下一次渲染能够显示降级后的 UI
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    // 你同样可以将错误日志上报给服务器
    logErrorToMyService(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      // 你可以自定义降级后的 UI 并渲染
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; 
  }
}

context

Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言

使用 context, 我们可以避免通过中间元素传递 props.

//创建context 
const ThemeContext = React.createContext('light');//一切是组件
...
 <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
...
////React 会往上找到最近的 context.Provider,然后使用它的值。
 static contextType = ThemeContext;
  render() {
    return <Button theme={this.context} />;
  }

/////消费多个context
  // 提供初始 context 值的 App 组件
    return (
      <ThemeContext.Provider value={theme}>
        <UserContext.Provider value={signedInUser}>
          <Layout />
        </UserContext.Provider>
      </ThemeContext.Provider>
    );
 <ThemeContext.Consumer>
      {theme => (
        <UserContext.Consumer>
          {user => (
            <ProfilePage user={user} theme={theme} />
          )}
        </UserContext.Consumer>
      )}
    </ThemeContext.Consumer>

Refs

Refs提供了一种方式,允许我们访问dom节点或在render方法中创建的react元素(react组件实例)。

何时使用Refs

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

避免使用 refs 来做任何可以通过声明式实现来完成的事情。

ref 的值根据节点的类型而有所不同:

  • ref 属性用于 HTML 元素时,构造函数中使用 React.createRef() 创建的 ref 接收底层 DOM 元素作为其 current 属性。
  • ref 属性用于自定义 class 组件时,ref 对象接收组件的挂载实例作为其 current 属性。
  • 你不能在函数组件上使用 ref 属性,因为他们没有实例。

为DOM元素添加ref和转发refs到DOM组件

////为DOM元素添加ref
class CustomTextInput extends React.Component {
  constructor(props) {
    this.textInput = React.createRef();
  }
  render() {
    // 告诉 React 我们想把 <input> ref 关联到构造器里创建的 `textInput` 上
    return (
      <div>
        <input
          type="text"
          ref={this.textInput} />
      </div>
    );
  }
}
////转发refs到dom组件
const FancyButton = React.forwardRef((props, ref) => (
  //forwardRef第二个参数 ref 只在使用 React.forwardRef 定义组件时存在。
  //常规函数和 class 组件不接收 ref 参数,且 props 中也不存在 ref。
  //Ref 转发不仅限于 DOM 组件,你也可以转发 refs 到 class 组件实例中
  <button ref={ref} className="FancyButton">
    {props.children}
  </button>
));

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

为class组件添加ref和转发refs到hoc组件

////为class组件添加ref
class CustomTextInput extends React.Component {
  constructor(props) {
    this.textInput = React.createRef();
  }
  render() {
    // 告诉 React 我们想把 <input> ref 关联到构造器里创建的 `textInput` 上
    return (
      <div>
      <input
      type="text"
      <CustomTextInput ref={this.textInput} />//CustomTextInput不能是是没有实例的函数组件
      </div>
    );
  }
}

回调refs

React 也支持另一种设置 refs 的方式,称为“回调 refs”。它能助你更精细地控制何时 refs 被设置和解除。

不同于传递 createRef() 创建的 ref 属性,你会传递一个函数。这个函数中接受 React 组件实例或 HTML DOM 元素作为参数,以使它们能在其他地方被存储和访问。

////为class组件添加ref
class CustomTextInput extends React.Component {
  constructor(props) {
this.callbackRef=null;//关键代码,callbackRef即是ref
    this.textInput = ele=>{
      this.callbackRef=ele;//关键代码
    };
  }
  render() {
    // 告诉 React 我们想把 <input> ref 关联到构造器里创建的 `textInput` 上
    return (
      <div>
      <input ref={el=>this.el=el}>//el是一个ref
      <input
      type="text"
      <CustomTextInput ref={this.textInput} />//CustomTextInput不能是是没有实例的函数组件
      </div>
    );
  }
}

Fragments

fragments等同于<></>,除了它支持key.key 是唯一可以传递给 Fragment 的属性。

function Glossary(props) {
  return (
    <dl>
      {props.items.map(item => (
        // 没有`key`,React 会发出一个关键警告
        <React.Fragment key={item.id}>
          <dt>{item.term}</dt>
          <dd>{item.description}</dd>
        </React.Fragment>
      ))}
    </dl>
  );
}

高阶组件HOC

它适用于在一个地方定义这个逻辑,并在许多组件之间共享它。

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

HOC 不需要关心数据的使用方式或原因,而被包装组件也不需要关心数据是怎么来的。

function logProps(WrappedComponent) {
  return class extends React.Component {
    componentDidUpdate(prevProps) {
      console.log('Current props: ', this.props);
      console.log('Previous props: ', prevProps);
    }
    render() {
      // 将 input 组件包装在容器中,而不对其进行修改。Good!
      return <WrappedComponent {...this.props} />;
    }
  }
}

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

高阶组件的约定:

  • 不要改变原始组件,使用组合。
  • 将自身不相关的props传递给被包裹的组件。
  • 最大化可组合性。
  • 不要在render中使用hoc,会导致性能问题和重新挂载组件会导致该组件及其所有子组件的状态丢失。
  • 务必复制静态方法,要在返回之前把这些方法拷贝到容器组件上
  • 注意refs不会被传递
  • 包装显示名称以便轻松调试
function withSubscription(WrappedComponent) {
  class WithSubscription extends React.Component {/* ... */}
  WithSubscription.displayName = `WithSubscription(${getDisplayName(WrappedComponent)})`;
  return WithSubscription;
}

function getDisplayName(WrappedComponent) {
  return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}

Portals

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

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

尽管 portal 可以被放置在 DOM 树中的任何地方,但在任何其他方面,其行为和普通的 React 子节点行为一致。由于 portal 仍存在于React 树, 且与DOM 树中的位置无关,那么无论其子节点是否是 portal,像 context 这样的功能特性都是不变的。

这包含事件冒泡。一个从 portal 内部触发的事件会一直冒泡至包含React 树的祖先,即便这些元素并不是DOM 树中的祖先。