React文档笔记2-高级指引

目录

  1. 无障碍辅助功能
  2. 代码分割
  3. 元素渲染
  4. 错误边界
  5. Refs 转发
  6. Fragments
  7. 高阶组件
  8. 与第三方库协同
  9. 深入 JSX
  10. 性能优化
  11. Portals
  12. Profiler API

1. 无障碍辅助功能

跳过,后面再补。。。

2. 代码分割

打包:

  • 将多个文件引入合并到一个单独文件中的过程。接着在页面上引入这个打包文件,整个应用即可一次性加载。
  • 但是如果文件体积过大,就会导致加载时间过长。
  • 代码分割:创建多个包,在运行时动态加载。

代码分割方式:

  • 最佳方式是通过动态import()语法
    // 使用之前
    import {
          
           add } from './math';
    console.log(add(16, 26));
    // 使用之后
    import("./math").then(math => {
          
          
      console.log(math.add(16, 26));
    });
    
    使用 Create React AppNext.js,该功能已开箱即用,无需配置。
    自己使用webpack: webpack代码分离
    webpack配置示例
    使用Babel,要使它能够解析动态 import语法而不是将其进行转换,需使用babel-plugin-syntax-dynamic-import插件
  • React.lazy
    • React.lazy 函数能让你像渲染常规组件一样处理动态引入(的组件)。
    • React.lazySuspense技术还不支持服务端渲染。如果你想要在使用服务端渲染的应用中使用,我们推荐 Loadable Components 这个库
    import React, {
          
           Suspense } from 'react';
    // 模块加载失败(如网络问题),它会触发一个错误
    // 通过异常捕获边界(Error boundaries)技术来处理这些情况
    import MyErrorBoundary from './MyErrorBoundary';
    // React.lazy 接受一个函数,这个函数需要动态调用 import()。
    // 它必须返回一个 Promise,该 Promise 需要 resolve 一个 defalut export 的 React 组件。
    const OtherComponent = React.lazy(() => import('./OtherComponent'));
    const AnotherComponent = React.lazy(() => import('./AnotherComponent'));
    
    function MyComponent() {
          
          
      return (
        <div>
            <MyErrorBoundary>
    	    // fallback 属性接受任何在组件加载过程中你想展示的 React 元素。
    	    // 你可以将 Suspense 组件置于懒加载组件之上的任何位置。
    	    // 你甚至可以用一个 Suspense 组件包裹多个懒加载组件。
    	      <Suspense fallback={
          
          <div>Loading...</div>}>
    	        <section>
    	          <OtherComponent />
    	          <AnotherComponent />
    	        </section>
    	      </Suspense>
           </MyErrorBoundary>
        </div>
      );
    }
    
    
  • 基于路由的代码分割
    import React, {
          
           Suspense, lazy } from 'react';
    import {
          
           BrowserRouter as Router, Route, Switch } from 'react-router-dom';
    
    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>
    );
    

命名导出: React.lazy目前只支持默认导出(default exports)。如果你想被引入的模块使用命名导出(named exports),你可以创建一个中间模块,来重新导出为默认模块。

// ManyComponents.js
export const MyComponent = /* ... */;
export const MyUnusedComponent = /* ... */;
// MyComponent.js
export {
    
     MyComponent as default } from "./ManyComponents.js";
// MyApp.js
import React, {
    
     lazy } from 'react';
const MyComponent = lazy(() => import("./MyComponent.js"));

3. Context

Context提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。
Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。

// 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() {
    
    
  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} />;
  }
}

一种无需 context的解决方案是将内部组件自身传递下去,因而中间组件无需知道内部组件的所需的传参,只有最顶层的组件才知道。

API

  • React.createContext

    const MyContext = React.createContext(defaultValue);
    
    • 创建一个 Context 对象。当React渲染一个订阅了这个Context对象的组件,这个组件会从组件树中离自身最近的那个匹配的 Provider中读取到当前的context值。
    • 只有当组件所处的树中没有匹配到Provider时,其 defaultValue参数才会生效。这有助于在不使用Provider包装组件的情况下对组件进行测试。
    • 注意:将 undefined传递给 Providervalue时,消费组件的defaultValue不会生效。
  • Context.Provider

    <MyContext.Provider value={
          
          /* 某个值 */}>
    
    • 每个Context对象都会返回一个Provider React 组件,它允许消费组件订阅 context的变化。
    • Provider接收一个value属性,传递给消费组件。一个 Provider可以和多个消费组件有对应关系。多个Provider也可以嵌套使用,里层的会覆盖外层的数据。
    • Providervalue值发生变化时,它内部的所有消费组件都会重新渲染。Provider及其内部 consumer 组件都不受制于shouldComponentUpdate 函数,因此当consumer 组件在其祖先组件退出更新的情况下也能更新。
  • Class.contextType
    挂载在 class 上的contextType 属性会被重赋值为一个由React.createContext() 创建的 Context对象。这能让你使用this.context来消费最近 Context上的那个值。你可以在任何生命周期中访问到它,包括 render函数中。

    class MyClass extends React.Component {
          
          
      componentDidMount() {
          
          
        let value = this.context;
        /* 在组件挂载完成后,使用 MyContext 组件的值来执行一些有副作用的操作 */
      }
      componentDidUpdate() {
          
          
        let value = this.context;
        /* ... */
      }
      componentWillUnmount() {
          
          
        let value = this.context;
        /* ... */
      }
      render() {
          
          
        let value = this.context;
        /* 基于 MyContext 组件的值进行渲染 */
      }
    }
    MyClass.contextType = MyContext;
    

    实验性的 public class fields语法,你可以使用static这个类属性来初始化你的contextType

    class MyClass extends React.Component {
          
          
      static contextType = MyContext;
      render() {
          
          
        let value = this.context;
        /* 基于这个值进行渲染工作 */
      }
    }
    
    
  • Context.Consumer

    <MyContext.Consumer>
      {
          
          value => /* 基于 context 值进行渲染*/}
    </MyContext.Consumer>
    
    • 这里,React组件也可以订阅到 context变更。这能让你在函数式组件中完成订阅 context
    • 这需要函数作为子元素(function as a child)这种做法。这个函数接收当前的context值,返回一个React 节点。传递给函数的 value值等同于往上组件树离这个context最近的 Provider 提供的value 值。如果没有对应的 Providervalue参数等同于传递给 createContext()defaultValue
  • Context.displayName
    context对象接受一个名为displayNameproperty,类型为字符串。React DevTools使用该字符串来确定context要显示的内容。

    const MyContext = React.createContext(/* some value */);
    MyContext.displayName = 'MyDisplayName';
    
    <MyContext.Provider> // "MyDisplayName.Provider" 在 DevTools 中
    <MyContext.Consumer> // "MyDisplayName.Consumer" 在 DevTools 中
    
    

4. 错误边界

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

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

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

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

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; 
  }
}

// 然后你可以将它作为一个常规组件去使用:
<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>

注意:

  • 注意错误边界仅可以捕获其子组件的错误,它无法捕获其自身的错误。如果一个错误边界无法渲染错误信息,则错误会冒泡至最近的上层错误边界,这也类似于 JavaScriptcatch {}的工作机制。
  • React 16 起,任何未被错误边界捕获的错误将会导致整个 React组件树被卸载。

5. Refs 转发

  • Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素。
  • Ref 转发是一项将 ref 自动地通过组件传递到其一子组件的技巧。
  • Ref 转发是一个可选特性,其允许某些组件接收 ref,并将其向下传递(换句话说,“转发”它)给子组件。

使用方法:

  • createRef
class App extends React.Component{
    
    
    constructor(props) {
    
    
        super(props);
        // 一般在构造函数中将refs分配给实例属性,以供组件的其它方法中使用
        this.myRef = React.createRef();
        console.log(this.myRef)
      }
      componentDidMount(){
    
    
        this.myRef.current.someMethod() // 也就是说,可以允许父组件调用子组件的方法
      }
      render() {
    
    
        return <Children ref={
    
    this.myRef} />;
      }
}
class Children extends React.Component{
    
    
    someMethod(){
    
    
        console.log(123)
    }
      render() {
    
    
        return <span>123</span>;
      }
}
  • 回调Refs
    React 将在组件挂载时,会调用 ref 回调函数并传入 DOM 元素,当卸载时调用它并传入 null。在 componentDidMountcomponentDidUpdate 触发前,React 会保证 refs 一定是最新的。

    class App extends React.Component{
          
          
        constructor(props) {
          
          
            super(props);
            this.targetRef = null
            this.myRef = (e)=> this.targetRef = e;
          }
          componentDidMount(){
          
          
            if(this.targetRef){
          
          
                this.targetRef.innerHTML = '123'
            }
          }
          render() {
          
          
            return <div ref={
          
          this.myRef} />;
          }
    }
    
  • String 类型的Refs(不推荐使用)

  • useRef (React Hooks)

    import {
          
           useRef } from 'react';
    function RefExample(props){
          
          
        const inputElement = useRef()
        return(<div>
            <input ref={
          
          inputElement}></input>
            <button onClick={
          
          ()=>{
          
          
                inputElement.current.focus()
            }}>focus</button>
        </div>)
    }
    
  • useRef vs createRef
    createRef每次渲染都会返回一个新的引用,而 useRef 每次都会返回相同的引用。

    function RefExample(props){
          
          
        const counterUseRef = useRef()
        const counterCreateRef = createRef()
        const [counter, setcounter] = useState(0)
        if(!counterUseRef.current){
          
          
            counterUseRef.current = counter
        }
        if(!counterCreateRef.current){
          
          
            counterCreateRef.current = counter
        }
        return(<div>
            <div>{
          
          counter}</div>
            <div>{
          
          counterUseRef.current}</div>
            <div>{
          
          counterCreateRef.current}</div>
            <div onClick={
          
          ()=>{
          
          
                setcounter(counter + 1)
            }}>add</div>
            </div>)
    }
    
    
    • forwardRef
    const FancyButton = React.forwardRef((props, ref) => (
      <button ref={
          
          ref} className="FancyButton">
        {
          
          props.children}
      </button>
    ));
    
    // You can now get a ref directly to the DOM button:
    const ref = React.createRef();
    <FancyButton ref={
          
          ref}>Click me!</FancyButton>;
    

使用场景:

  • 管理焦点,文本选择或媒体播放。
  • 触发强制动画。
  • 集成第三方 DOM 库。
    总而言之,都是需要获取到DOM实例的场景

能使用react数据流完成的操作,尽量不依赖ref

6. Fragments

React 中的一个常见模式是一个组件返回多个元素。Fragments 允许你将子列表分组,而无需向 DOM 添加额外节点。
作用类似vue里的<template>

// ...
render() {
  return (
    <React.Fragment>
      <ChildA />
      <ChildB />
      <ChildC />
    </React.Fragment>
  );
}
// ...
// 简写 注意:不支持 key 或属性。
  render() {
    return (
      <>
        <td>Hello</td>
        <td>World</td>
      </>
    );
  }
  // ...

7. 高阶组件

  • 高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。
  • HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式
  • 具体而言,高阶组件是参数为组件,返回值为新组件的函数

组件是将 props 转换为 UI,而高阶组件是将组件转换为另一个组件。

const EnhancedComponent = higherOrderComponent(WrappedComponent);

注意:

  • HOC 不会修改传入的组件,也不会使用继承来复制其行为。相反,HOC 通过将组件包装在容器组件中来组成新组件。HOC 是纯函数,没有副作用。
  • 不要改变原始组件。使用组合。
  • 约定:将不相关的 props 传递给被包裹的组件
  • 约定:包装显示名称以便轻松调试
  • 不要在 render 方法中使用 HOC
  • 务必复制静态方法
  • Refs 不会被传递

8. 与第三方库协同

React 可以被用于任何 web 应用中。它可以被嵌入到其他应用,且需要注意,其他的应用也可以被嵌入到 React。

9. 深入 JSX

实际上,JSX仅仅只是 React.createElement(component, props, ...children) 函数的语法糖。

10. 性能优化

11. Portals

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

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

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

12. Profiler API

Profiler 测量渲染一个 React应用多久渲染一次以及渲染一次的“代价”。
它的目的是识别出应用中渲染较慢的部分,或是可以使用类似 memoization优化的部分,并从相关优化中获益。

注意:Profiling 增加了额外的开支,所以它在生产构建中会被禁用。

render(
  <App>
    <Profiler id="Navigation" onRender={callback}>
      <Navigation {...props} />
    </Profiler>
    <Profiler id="Main" onRender={callback}>
      <Main {...props} />
    </Profiler>
  </App>
);

Profiler 能添加在 React树中的任何地方来测量树中这部分渲染所带来的开销。
它需要两个prop
一个是id(string)
一个是当组件树中的组件“提交”更新的时候被React调用的回调函数 onRender(function)

猜你喜欢

转载自blog.csdn.net/weixin_40693643/article/details/114650977