Wsh's blog Wsh's blog
首页
  • 基础知识
  • ArkUI
  • UIAbility
  • 组件通信方式
  • 前端缓存
  • React
  • typescript
  • javascript
  • flutter
  • node
  • webpack
web3D😉
宝库📰
  • 分类
  • 标签
  • 归档
龙哥的大🐂之路 (opens new window)
GitHub (opens new window)

wsh

热爱前端的程序媛
首页
  • 基础知识
  • ArkUI
  • UIAbility
  • 组件通信方式
  • 前端缓存
  • React
  • typescript
  • javascript
  • flutter
  • node
  • webpack
web3D😉
宝库📰
  • 分类
  • 标签
  • 归档
龙哥的大🐂之路 (opens new window)
GitHub (opens new window)
  • react基础

    • react工具API
    • react组件
      • react生命周期
      • react 事件机制
    • react更新特性

    • react进阶

    • react
    • react基础
    2022-02-07
    目录

    react组件

    # 01. Component

    packages/react/src/ReactBaseClasses.js

    // 用于更新组件状态的基类帮助程序
    function Component(props, context, updater) {
      this.props = props;
      this.context = context;
      // If a component has string refs, we will assign a different object later.
      this.refs = emptyObject; // 如果一个组件有字符串引用,后面指定一个不同的对象。
      this.updater = updater || ReactNoopUpdateQueue; // updater 对象上保存着更新租金的方法
    }
    
    Component.prototype.setState = function(partialState, callback) {
      this.updater.enqueueSetState(this, partialState, callback, 'setState');
    };
    
    Component.prototype.forceUpdate = function(callback) {
      this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
    };
    
    • Component 底层 React 的处理逻辑是: 类组件执行构造函数过程中会在实例上绑定 props 和 context ,初始化置空 refs 属性,原型链上绑定setState、forceUpdate 方法。对于 updater,React 在实例化类组件之后会单独绑定 update 对象.

    所以说 为什么调用 super(props),由此而来。

    constructor(){
        super()
        console.log(this.props) // 打印 undefined 为什么?
    }
    

    绑定 props 是在父类 Component 构造函数中,执行 super 等于执行 Component 函数,此时 props 没有作为第一个参数传给 super() ,在 Component 中就会找不到 props 参数,从而变成 undefined。

    • 调用super的原因:在ES6中,在子类的constructor中必须先调用super才能引用this
    • super(props)的目的:在constructor中可以使用this.props

    如何被实例化?源码如下:

    packages/react-reconciler/src/ReactFiberClassComponent.new.js

    function constructClassInstance (workInProgress, ctor, props) {
        ...
        let instance = new ctor(props, context);
        adoptClassInstance(workInProgress, instance);
        ...
    }
    
    function adoptClassInstance(workInProgress, instance) {
      instance.updater = classComponentUpdater;
    }
    
    const classComponentUpdater = {
      isMounted,
      enqueueSetState(inst, payload, callback) {
        // setState 触发
      }
      enqueueReplaceState(inst, payload, callback) {
        // replaceState 触发
        update.tag = ReplaceState;
      }
      enqueueForceUpdate(inst, payload, callback) {
        // forceUpdate 触发
        update.tag = ForceUpdate;
      }
    }
    

    enqueueSetState/enqueueReplaceState/enqueueForceUpdate 实现方式基本一致

    1. 更新过期时间
    2. 创建Update对象
    3. 为update对象绑定一些属性,比如 tag 、callback
    4. 创建的update对象入队 (enqueueUpdate)
    5. 进入调度过程

    # 02. PureComponent

    PureComponent 和Component用法差不多,唯一不同的是,纯组件PureComponent会进行浅比较,props 和state是否相同是否重新渲染组件。一般用于于性能优化,减少render次数。如果对象包含复杂的数据结构(比如对象和数组),会浅比较,如果深层次的改变,是无法作出判断的,React.PureComponent 认为没有变化,而没有渲染试图。我们可以借助于immetable.js,提高对象的比较性能,用 immetable.js 配合 shouldComponentUpdate 或者 react.memo来使用, 通过immetable.js里面提供的 is 方法来判断,前后对象数据类型是否发生变化

    import React, { PureComponent } from "react";
    class Index extends PureComponent {
      constructor(props) {
        super(props);
        this.state = {
          user: {
            name: "wsh",
            age: 18,
          }
        };
        this.handerClick = this.handerClick.bind(this);
      }
      handerClick = () => {
        const { user } = this.state;
        user.age++;
        // this.setState({ user: {...user} });
        this.setState({ user }); // 不更新的原因:两次user对象,都指向同一个user,没有发生改变,所以不更新视图
      };
      render() {
        const { user } = this.state;
        return (
          <div>
            <div>
              name: {user.name}
              age: {user.age}
            </div>
            <button onClick={this.handerClick}>点击</button>
          </div>
        );
      }
    }
    
    export default Index;
    

    # 03. memo

    • React.memo 与PureComponent作用类似,可以用作性能优化.

    • React.memo 是高阶组件,函数组件和类组件都可以使用,PureComponent是 只能用作类组件

    • React.memo只能对props的情况确定是否渲染,而PureComponent是针对props和state

    • React.memo([组件本身],param),第一个参数是原始组件本身,第二个参数是通过对比props是否相等来对比是否渲染(pre,next)=> { return true or false } 与shouldComponentUpdate 相反,返回true,说明props没有发生变化,不渲染,返回false 选择组件

      React.memo: 第二个参数 返回 true 组件不渲染 , 返回 false 组件重新渲染。 shouldComponentUpdate: 返回 true 组件渲染 , 返回 false 组件不渲染。

    React.memo在一定程度上,等价于在组件外部使用shouldComponentUpdate,用于拦截新老props,确定组件是否更新。

    import React, { Component, memo } from "react";
    
    function TextMemo(props) {
      console.log("子组件渲染");
      if (props) {
        return <div>hello react</div>;
      }
    }
    const controllIsRender = (pre, next) => {
      if (pre.age === next.age) {
        return true;
      } else if (next.age > 5) {
        return true;
      } else {
        return false;
      }
    };
    const NewMemo = memo(TextMemo, controllIsRender);
    
    class Index extends Component {
      constructor(props) {
        super(props);
        this.state = {
          count: 1,
          age: 1,
        };
      }
      render() {
        const { count, age } = this.state;
        return (
          <div>
            <div>
              改变count:当前值 {count}
              <button onClick={() => this.setState({ count: count + 1 })}>count++</button>
              <button onClick={() => this.setState({ count: count - 1 })}>count--</button>
            </div>
            <div>
              改变age:当前后 {age}
              <button onClick={() => this.setState({ age: age + 1 })}>age++</button>
              <button onClick={() => this.setState({ age: age - 1 })}>age--</button>
            </div>
            <NewMemo age={age} count={count} />
          </div>
        );
      }
    }
    
    export default Index;
    
    

    # 04. forwardRef

    # 场景一:隔代获取ref引用

    • 正常情况下:react不允许ref 通过props 传递,因为在组件上已经带有ref这个属性,在组件进行调和过程中,已经被特殊处理了。
    • forwardRef 出现可以解决这个问题,把ref转发到自定义的forwardRef定义的属性上,可以让ref通过props进行传递
    import React, { Component, forwardRef } from "react";
    function Son(props) {
      const { grandRef } = props;
      return <div>
          <span ref={grandRef}>Son</span>
      </div>;
    }
    class Father extends React.Component {
      constructor(props) {
        super(props);
      }
    
      render() {
        return <Son grandRef={this.props.grandRef} />;
      }
    }
    const NewFather = forwardRef((props, ref) => (
      <Father grandRef={ref} {...props} />
    ));
    
    export default class GrandFather extends Component {
      constructor(props) {
        super(props);
      }
      node = null;
      componentDidMount() {
          console.log(this.node)
      }
      render() {
        return <div>
            <NewFather ref={(node) => this.node = node}/>
            <Father/>
        </div>;
      }
    }
    
    

    # 场景二:高阶组件转发ref

    由于属性代理的hoc,被包裹一层,如果是类组件,通过ref拿不到原始组件的实例的,可以借助forWardRef转发ref拿到对应的实例

    import React, { Component, useRef, useEffect } from 'react'
    function HOC(Comp){
        class Wrap extends Component {
            render() {
                const {forwardRef, ...otherProps} = this.props;
                return (
                    <Comp ref={forwardRef} {...otherProps}/>
                )
            }
        }
        return React.forwardRef((props, ref) => <Wrap forwardRef={ref} {...props}/>)
    }
    class Index extends Component{
        componentDidMount(){
            console.log('hello react')
        }
        render() {
            return (
                <div>hello forwardRef</div>
            )
        }
    }
    const HocIndex = HOC(Index);
    export default () => {
        const node = useRef(null);
        useEffect(() => {
          console.log(node.current.componentDidMount(), '--')
        }, [])
        return <div>
            <HocIndex ref={node}/>
        </div>
    }
    
    

    # 05. lazy

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

    import React, { Component, Suspense } from 'react'
    const UserInfo = React.lazy(() => new Promise((resolve)=>{
      setTimeout(() => {
        resolve(import('../Component/UserInfo'))
      }, 1000)
      
    }))
    export default class Index extends Component {
      render() {
        return (
          <Suspense fallback="loading...">
            <UserInfo/>
          </Suspense>
        )
      }
    }
    
    
    • import 原理如下: import() 函数是由TS39提出的一种动态加载模块的规范实现,其返回是一个 promise。在浏览器宿主环境中一个import()的参考实现如下:
    function import(url) {
        return new Promise((resolve, reject) => {
          const script = document.createElement('script');
          // toString(32) 32进制
          const tmpGlobal = "__tempModuleLoadingVariable"+ Math.random().toString(32).substring(2);
          script.type = "module";
          script.textContent = `import * as m from "${url}"; window.${tmpGlobal} = m;`;
          script.onload = () => {
              resolve(window[tmpGlobal]);
              delete(window[tmpGlobal]);
              script.remove();
          }
          script.onerror = () => {
              reject(new Error("Failed to load module script with URL " + url));
              delete(window[tmpGlobal]);
              script.remove();
          }
          document.documentElement.appendChild(script)
        })
    }
    

    # 06. Fragment

    不增加额外的dom节点,能够让一个组件返回多个元素 写法:

    <React.Fragment></React.Fragment>
    或者
    <></>
    

    二者区别:

    • Fragment支持key元素,<></>不支持key元素

    通过我们在map数据时,react底层会处理 默认在外部嵌套一个[Fragment]标签 例如:

     [1,3,4].map(v => <span key={v}>{v}</span>)
     等价于
     <Fragment>
      <span></span>
      <span></span>
      <span></span>
    </Fragment>
    

    # 07. Profiler

    Profiler react 性能审查工具

    react 有两个阶段为我们的应用工作

    • render:React通过将渲染结果与先前的渲染进行比较来确定需要进行哪些DOM更改
    • commit:React应用需要进行的任何更改。 从DOM中添加/删除并调用生命周期挂钩,例如componentDidMount和componentDidUpdate

    profiler DevTools 是在commit 阶段收集性能数据的。各次 commit 会被展示在界面顶部的条形图中

    Profiler 一般有两个参数:

    • id:用来标识 Profiler的唯一性
    • onRender:用于渲染完成,接受渲染参数

    Profiler 需要一个 onRender 函数作为参数。 React 会在 profile 包含的组件树中任何组件 “提交” 一个更新的时候调用这个函数。 它的参数描述了渲染了什么和花费了多久。官网 (opens new window)

    function onRenderCallback(
      id, // 发生提交的 Profiler 树的 “id”
      phase, // "mount" (如果组件树刚加载) 或者 "update" (如果它重渲染了)之一
      actualDuration, // 本次更新 committed 花费的渲染时间
      baseDuration, // 估计不使用 memoization 的情况下渲染整颗子树需要的时间
      startTime, // 本次更新中 React 开始渲染的时间
      commitTime, // 本次更新中 React committed 的时间
      interactions // 属于本次更新的 interactions 的集合
    ) {
      // 合计或记录渲染时间。。。
    }
    
    • id: string - 发生提交的 Profiler 树的 id。 如果有多个 profiler,它能用来分辨树的哪一部分发生了“提交”。
    • phase: "mount" | "update" - 判断是组件树的第一次装载引起的重渲染,还是由 props、state 或是 hooks 改变引起的重渲染。
    • actualDuration: number - 本次更新在渲染 Profiler 和它的子代上花费的时间。 这个数值表明使用 memoization 之后能表现得多好。(例如 React.memo,useMemo,shouldComponentUpdate)。 理想情况下,由于子代只会因特定的 prop 改变而重渲染,因此这个值应该在第一次装载之后显著下降。
    • baseDuration: number - 在 Profiler 树中最近一次每一个组件 render 的持续时间。 这个值估计了最差的渲染时间。(例如当它是第一次加载或者组件树没有使用 memoization)。
    • startTime: number - 本次更新中 React 开始渲染的时间戳。
    • commitTime: number - 本次更新中 React commit 阶段结束的时间戳。 在一次 commit 中这个值在所有的 profiler 之间是共享的,可以将它们按需分组。
    • interactions: Set - 当更新被制定时,“interactions” 的集合会被追踪。(例如当 render 或者 setState 被调用时)( 能用来识别更新是由什么引起的,实验性)。

    # 08. StrictMode

    作用:用来检测项目中潜在的问题,与 Fragment 一样, StrictMode 不会渲染任何可见的UI 。它为其后代元素触发额外的检查和警告

    注意:

    严格模式检查仅在开发模式下运行;它们不会影响生产构建。

    可以为应用程序的任何部分启用严格模式

    import React from 'react';
    
    function ExampleApplication() {
      return (
        <div>
          <Header />
          <React.StrictMode>
            <div>
              <ComponentOne />
              <ComponentTwo />
            </div>
          </React.StrictMode>
          <Footer />
        </div>
      );
    }
    
    

    StrictMode 目前有助于

    • 识别不安全的生命周期(UNSAFE_componentWillMount, UNSAFE_componentWillReceiveProps, UNSAFE_componentWillUpdate)
    • 关于使用过时字符串 ref API 的警告
    • 关于使用废弃的 findDOMNode 方法的警告
    • 检测意外的副作用
    • 检测过时的 context API
    #react
    react工具API
    react生命周期

    ← react工具API react生命周期→

    最近更新
    01
    组件通信方式
    01-07
    02
    UIAbility
    01-07
    03
    ATKTS
    01-06
    更多文章>
    Theme by Vdoing | Copyright © 2022-2025 Wsh | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式