React之state、hooks性能分析

2023-11-19

目录

 

一、state

1、为什么使用setState

2、setState异步更新

3、如何获取异步的结果

4、setState一定是异步吗?

5、源码分析

6、数据的合并

7、多个state的合并

二、为什么需要Hook?

三、Class组件存在的问题

四、Hook的出现

五、案例对比

六、useState

1、案例分析

2、认识useState

七、Effect Hook

1、认识Effect Hook

2、需要清除Effect

3、使用多个Effect

4、Effect性能优化

八、useContext

九、useReducer

十、useCallback

十一、useMemo

十二、useRef

十三、useImperativeHandle

十四、useLayoutEffect

十五、自定义Hook

1、Context的共享

2、获取鼠标滚动位置

3、localStorage数据存储

十六、useState源码分析

十七、redux hooks


一、state

1、为什么使用setState

  1. 开发中我们并不能直接通过修改state的值来让界面发生更新:

    因为我们修改了state之后,希望React根据最新的State来重新渲染界面,但是这种方式的修改React并不知道数据发生了变 化;
     
    import React, { Component } from 'react'
    
    export default class App extends Component {
      constructor(props){
        super(props)
        this.state={
          counter:0
        }
      }
      increament = ()=> {
        this.state.counter += 1;
        console.log(this.state.counter)
      }
      render() {
        return (
          <div>
            <h2>当前计数:{this.state.counter}</h2>
            <button onClick={this.increament}>+1</button>
          </div>
        )
      }
    }
    
    点击按钮后,界面并有刷新,但是counter的值确实已经改变了


    因此,我们必须通过setState来告知React数据已经发生了变化;
    this.setState({
          counter : this.state.counter + 1
    })

     
  2. 在组件中并没有实现setState的方法,setState方法是从Component中继承过来的。



     

2、setState异步更新

  1. setState的更新是异步的?
     
    import React, { Component } from 'react';
    
    export default class App extends Component {
      constructor(props) {
        super(props);
        this.state = {
          message: 'init message'
        };
      }
      changeMessage = () => {
        this.setState({
          message: 'changeMessage',
        });
        console.log(this.state.message);
      };
      render() {
        return (
          <div>
            <h2>当前计数:{this.state.message}</h2>
            <button onClick={this.changeMessage}>按钮</button>
          </div>
        );
      }
    }
    




    最终打印结果是init message

    可见setState是异步的操作,我们并不能在执行完setState之后立马拿到最新的state的结果
  2. 为什么setState设计为异步呢?

    setState设计为异步其实之前在GitHub上也有很多的讨论;

    React核心成员(Redux的作者)Dan Abramov也有对应的回复(传送门);

    总结如下:

    1)setState设计为异步,可以显著的提升性能;

    如果每次调用 setState都进行一次更新,那么意味着render函数会被频繁调用,界面重新渲染,这样效率是很低的;

    最好的办法应该是获取到多个更新,之后进行批量更新;

    2)如果同步更新了state,但是还没有执行render函数,那么state和props不能保持同步;

    state和props不能保持一致性,会在开发中产生很多的问题;
     
    import React, { Component } from 'react';
    
    function Home(props) {
      // 在父组件的render还未执行时,此处值仍然是init message 
      return <h2>{props.message}</h2>;
    }
    
    export default class App extends Component {
      constructor(props) {
        super(props);
        this.state = {
          message: 'init message',
        };
      }
      changeMessage = () => {
        this.setState({
          message: 'changeMessage',
        });
        //如果是同步,则此处state中的message已经是changeMessage,而由于render执行滞后,
        //会导致在render还未执行时,Home组建props中的message还是init message,在这个滞后期间,App中的message和Home中的message值不同步
        console.log(this.state.message);
      };
      render() {
        return (
          <div>
            <h2>当前计数:{this.state.message}</h2>
            <Home message={this.state.message} />
            <button onClick={this.changeMessage}>按钮</button>
          </div>
        );
      }
    }
    

3、如何获取异步的结果

  1. 方式一:setState的回调

    setState接受两个参数:第二个参数是一个回调函数,这个回调函数会在更新后会执行;

    格式如下:setState(partialState, callback)
     
      changeMessage = () => {
        //方式1:获取异步更新后的数据
        // setState(更新state,回调函数)
        this.setState({
          message: 'changeMessage',
        } ,()=> {
          console.log(this.state.message) //changeMessage
        });
      };
  2. 当然,我们也可以在生命周期函数:
     
    componentDidUpdate
    完整代码:
    import React, { Component } from 'react';
    
    export default class App extends Component {
      constructor(props) {
        super(props);
        this.state = {
          message: 'init message',
        };
      }
    
      componentDidUpdate(){
        console.log(this.state.message) //changeMessage
      }
      
      changeMessage = () => {
        //方式1:获取异步更新后的数据
        // setState(更新state,回调函数)
        this.setState({
          message: 'changeMessage',
        } ,()=> {
          console.log(this.state.message) //changeMessage
        });
      };
      render() {
        return (
          <div>
            <h2>当前计数:{this.state.message}</h2>
            <button onClick={this.changeMessage}>按钮</button>
          </div>
        );
      }
    }

     

4、setState一定是异步吗?

  1.  验证一:在setTimeout中的更新:
      changeMessage = () => {
        // 情况1:将setState放入计时器中
        setTimeout(() => {
          this.setState({ message: 'changeMessage' });
          console.log(this.state.message) //changeMessage
        }, 0);
      };
  2. 验证二:原生DOM事件:
     document.getElementById('btn').addEventListener('click',()=>{
          console.log('btn被点击')
          this.setState({ message: 'changeMessage' });
          console.log(this.state.message) //changeMessage
     })

    完整代码:
     

    import React, { Component } from 'react';
    
    export default class App extends Component {
      constructor(props) {
        super(props);
        this.state = {
          message: 'init message',
        };
      }
      
      changeMessage = () => {
        // 情况1:将setState放入计时器中
        setTimeout(() => {
          this.setState({ message: 'changeMessage' });
          console.log(this.state.message) //changeMessage
        }, 0);
      };
    
      componentDidMount(){
        document.getElementById('btn').addEventListener('click',()=>{
          console.log('btn被点击')
          this.setState({ message: 'changeMessage' });
          console.log(this.state.message) //changeMessage
        })
      }
    
      render() {
        return (
          <div>
            <h2>当前计数:{this.state.message}</h2>
            <button onClick={this.changeMessage}>按钮</button>
            <button id='btn'>按钮2</button>
          </div>
        );
      }
    }

     

  3. 其实分成两种情况:

     在组件生命周期或React合成事件中,setState是异步;

    在setTimeout或者原生dom事件中,setState是同步

5、源码分析

6、数据的合并

我通过setState去修改message,是不会对name产生影响的;

源码中其实是有对 原对象 和 新对象进行合并的:

 

7、多个state的合并

比如我们还是有一个counter属性,记录当前的数字:

 

二、为什么需要Hook?

Hook 是 React 16.8 的新增特性,它可以让我们在不编写class的情况下使用state以及其他的React特性(比如生命周期)。

class组件相对于函数式组件有什么优势?比较常见的是下面的优势:
 

  1. class组件可以定义自己的state,用来保存组件自己内部的状态;

    函数式组件不可以,因为函数每次调用都会产生新的临时变量;
     
    function Home() {
      // 局部变量 ,函数调用 变量会重新被定义
      let a = 0;
      return (
        <>
          <h2>aaaa</h2>
          <button>+1</button>
        </>
      );
    }
  2. class组件有自己的生命周期,我们可以在对应的生命周期中完成自己的逻辑;

    比如在componentDidMount中发送网络请求,并且该生命周期函数只会执行一次;

    函数式组件在没有hooks情况下,如果在函数中发送网络请求,意味着每次重新渲染都会重新发送一次网络请求;
  3. class组件可以在状态改变时只会重新执行render函数以及我们希望重新调用的生命周期函数componentDidUpdate等;

    函数式组件在重新渲染时,整个函数都会被执行,似乎没有什么地方可以只让它们调用一次;
  4. 所以,在Hook出现之前,对于上面这些情况我们通常都会编写class组件

三、Class组件存在的问题

  1. 复杂组件变得难以理解:

    我们在最初编写一个class组件时,往往逻辑比较简单,并不会非常复杂。但是随着业务的增多,我们的class组件会变得越来越复杂;

    比如componentDidMount中,可能就会包含大量的逻辑代码:包括网络请求、一些事件的监听(还需要在 componentWillUnmount中移除);

    而对于这样的class实际上非常难以拆分:因为它们的逻辑往往混在一起,强行拆分反而会造成过度设计,增加代码的复杂度;
  2. 难以理解的class:

    理解ES6的class是学习React的一个障碍。

    比如在class中,我们必须搞清楚this的指向到底是谁,所以需要花很多的精力去学习this;
     
  3. 组件复用状态很难

    为了一些状态的复用我们需要通过高阶组件或render props;

    像redux中connect或者react-router中的withRouter,这些高阶组件设计的目的就是为了状态的复用;

    或者类似于Provider、Consumer来共享一些状态,但是多次使用Consumer时,我们的代码就会存在很多嵌套;

    这些代码让我们不管是编写和设计上来说,都变得非常困难;

四、Hook的出现

  1. Hook的出现,可以解决上面提到的这些问题;
  2. 简单总结一下hooks:

    它可以让我们在不编写class的情况下使用state以及其他的React特性

    但是我们可以由此延伸出非常多的用法,来让我们前面所提到的问题得到解决;
  3. Hook的使用场景:

    Hook的出现基本可以代替我们之前所有使用class组件的地方(除了一些非常不常用的场景);

    但是如果是一个旧的项目,你并不需要直接将所有的代码重构为Hooks,因为它完全向下兼容,你可以渐进式的来使用它;

    Hook只能在函数组件中使用,不能在类组件,或者函数组件之外的地方使用;

五、案例对比

  我们通过一个计数器案例,来对比一下class组件和函数式组件结合hooks的对比:
class
import React, { Component } from 'react';

export default class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      message: 'init message',
    };
  }

  changeMessage = () => {
    this.setState({ message: 'changeMessage' });
  };

  render() {
    return (
      <div>
        <h2>当前计数:{this.state.message}</h2>
        <button onClick={this.changeMessage}>按钮</button>
      </div>
    );
  }
}
函数式
import React from 'react';

export default () => {
  const [message, setMessage] = useState('init message');
  const changeMessage = () => {
    setMessage('changeMessage');
  };
  return (
    <div>
      <h2>当前计数:{message}</h2>
      <button onClick={changeMessage}>按钮</button>
    </div>
  );
}
你会发现上面的代码差异非常大:函数式组件结 合hooks 让整个代码变得非常简洁,并且再也不 用考虑this 相关的问题;

 

 

六、useState

1、案例分析

  1. 那么我们来研究一下核心的一段代码代表什么意思:

    useState来自react,需要从react中导入,它是一个hook;

    参数:初始化值,如果不设置为undefined;

    返回值:数组,包含两个元素; 元素一:当前状态的值(第一调用为初始化值); 元素二:设置状态值的函数;

    点击button按钮后,会完成两件事情: 调用setCount,设置一个新的值; 组件重新渲染,并且根据新的值返回DOM结构;
  2. Hook 就是 JavaScript 函数,这个函数可以帮助你 钩入(hook into) React State以及生命周期等特性;
  3. 但是使用它们会有两个额外的规则:

    只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。

    只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。
  4. Hook指的类似于useState、 useEffect这样的函数 ,Hooks是对这类函数的统称

2、认识useState

  1. State Hook的API就是 useState:

    useState会帮助我们定义一个 state变量,useState 是一种新方法,它与 class 里面的 this.state 提供的功能完全相同。一

    般来说,在函数退出后变量就会”消失”,而 state 中的变量会被 React 保留。

    useState接受唯一一个参数,在第一次组件被调用时使用来作为初始化值。(如果没有传递参数,那么初始化值为
    undefined)。

    useState是一个数组,我们可以通过数组的解构,来完成赋值会非常方便。 (传送门)
  2. 为什么叫 useState 而不叫 createState?

    “Create” 可能不是很准确,因为 state 只在组件首次渲染的时候被创建。

    在下一次重新渲染时,useState 返回给我们当前的 state。

    如果每次都创建新的变量,它就不是 “state”了。

    这也是 Hook 的名字总是以 use 开头的一个原因。
  3. 当然,我们也可以在一个组件中定义多个变量和复杂变量(数组、对象)
  4. 多状态:
    import React, { useState } from 'react';
    
    export default function MultiHookState() {
    
      const [count, setCount] = useState(0);
      const [age, setAge] = useState(18);
      const [friends, setFriends] = useState(["kobe", "lilei"]);
    
      return (
        <div>
          <h2>当前计数: {count}</h2>
          <h2>我的年龄: {age}</h2>
          <ul>
            {
              friends.map((item, index) => {
                return <li key={item}>{item}</li>
              })
            }
          </ul>
        </div>
      )
    }
  5. 复杂状态修改
    import React, { useState } from 'react';
    
    export default function ComplexHookState() {
      const [friends, setFrineds] = useState(['kobe', 'lilei']);
      const [students, setStudents] = useState([
        { id: 110, name: 'why', age: 18 },
        { id: 111, name: 'kobe', age: 30 },
        { id: 112, name: 'lilei', age: 25 },
      ]);
    
      function addFriend() {
        friends.push('hmm');
        setFrineds(friends);
      }
    
      function incrementAgeWithIndex(index) {
        const newStudents = [...students];
        newStudents[index].age += 1;
        setStudents(newStudents);
      }
    
      return (
        <div>
          <h2>好友列表:</h2>
          <ul>
            {friends.map((item, index) => {
              return <li key={index}>{item}</li>;
            })}
          </ul>
          <button onClick={(e) => setFrineds([...friends, 'tom'])}>添加朋友</button>
          {/* 错误的做法 */}
          <button onClick={addFriend}>添加朋友</button>
    
          <h2>学生列表</h2>
          <ul>
            {students.map((item, index) => {
              return (
                <li key={item.id}>
                  <span>
                    名字: {item.name} 年龄: {item.age}
                  </span>
                  <button onClick={(e) => incrementAgeWithIndex(index)}>
                    age+1
                  </button>
                </li>
              );
            })}
          </ul>
        </div>
      );
    }
    

     

 

七、Effect Hook

 

1、认识Effect Hook

  1. Effect Hook 可以让你来完成一些类似于class中生命周期的功能;

    事实上,类似于网络请求、手动更新DOM、一些事件的监听,都是React更新DOM的一些副作用(Side Effects);

    所以对于完成这些功能的Hook被称之为 Effect Hook;
  2. 假如我们现在有一个需求:页面的title总是显示counter的数字,分别使用class组件和Hook实现:
     
    import React, { PureComponent } from 'react';
    
    export default class ClassCounterTitleChange extends PureComponent {
      constructor(props) {
        super(props);
    
        this.state = {
          counter: 0,
        };
      }
    
      componentDidMount() {
        // 1.修改DOM
        document.title = this.state.counter;
    
        // 2.订阅事件
        console.log('订阅一些事件');
    
        // 3.网络请求
      }
    
      componentWillUnmount() {
        console.log('取消事件订阅');
      }
    
      componentDidUpdate() {
        document.title = this.state.counter;
      }
    
      render() {
        return (
          <div>
            <h2>当前计数: {this.state.counter}</h2>
            <button
              onClick={(e) => this.setState({ counter: this.state.counter + 1 })}
            >
              +1
            </button>
          </div>
        );
      }
    }
    
    import React, { useState, useEffect } from 'react';
    
    export default function HookCounterChangeTitle() {
      const [counter, setCounter] = useState(0);
    
      useEffect(() => {
        document.title = counter;
      });
    
      return (
        <div>
          <h2>当前计数: {counter}</h2>
          <button onClick={(e) => setCounter(counter + 1)}>+1</button>
        </div>
      );
    }

     

  3. useEffect的解析:

    通过useEffect的Hook,可以告诉React需要在渲染后执行某些操作;

    useEffect要求我们传入一个回调函数,在React执行完更新DOM操作之后,就会回调这个函数;

    默认情况下,无论是第一次渲染之后,还是每次更新之后,都会执行这个 回调函数;

2、需要清除Effect

import React, { useEffect, useState } from 'react';

export default function EffectHookCancelDemo() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log('订阅一些事件');

    return () => {
      console.log('取消订阅事件');
    };
  }, []);

  return (
    <div>
      <h2>EffectHookCancelDemo</h2>
      <h2>{count}</h2>
      <button onClick={(e) => setCount(count + 1)}>+1</button>
    </div>
  );
}
  1. 在class组件的编写过程中,某些副作用的代码,我们需要在componentWillUnmount中进行清除:

    比如我们之前的事件总线或Redux中手动调用subscribe;

    都需要在componentWillUnmount有对应的取消订阅;

    Effect Hook通过什么方式来模拟componentWillUnmount呢?
  2. useEffect传入的回调函数A本身可以有一个返回值,这个返回值是另外一个回调函数B
  3. type EffectCallback = () => (void | (() => void | undefined));
  4. 为什么要在 effect 中返回一个函数?

    这是 effect 可选的清除机制。每个 effect 都可以返回一个清除函数;

    如此可以将添加和移除订阅的逻辑放在一起;

    它们都属于 effect 的一部分;
  5. React 何时清除 effect?

     React 会在组件更新和卸载的时候执行清除操作;

    effect 在每次渲染的时候都会执行;

3、使用多个Effect

  1. 使用Hook的其中一个目的就是解决class中生命周期经常将很多的逻辑放在一起的问题:

    比如网络请求、事件监听、手动修改DOM,这些往往都会放在componentDidMount中;
  2. 使用Effect Hook,我们可以将它们分离到不同的useEffect中:
  3. Hook 允许我们按照代码的用途分离它们, 而不是像生命周期函数那样:

    React 将按照 effect 声明的顺序依次调用组件中的每一个 effect;

4、Effect性能优化

  1. 默认情况下,useEffect的回调函数会在每次渲染时都重新执行,但是这会导致两个问题:

    某些代码我们只是希望执行一次即可,类似于componentDidMount和componentWillUnmount中完成的事情;(比如网络请求、订阅和取消订阅);

    另外,多次执行也会导致一定的性能问题;
  2. 我们如何决定useEffect在什么时候应该执行和什么时候不应该执行呢?

    useEffect实际上有两个参数:

    参数一:执行的回调函数;

    参数二:该useEffect在哪些state发生变化时,才重新执行;(受谁的影响)
  3. 但是,如果一个函数我们不希望依赖任何的内容时,也可以传入一个空的数组 []:

    那么这里的两个回调函数分别对应的就是componentDidMount和componentWillUnmount生命周期函数了;
  4. 案例
    import React, { useState, useEffect } from 'react'
    
    export default function MultiEffectHookDemo() {
      const [count, setCount] = useState(0);
      const [isLogin, setIsLogin] = useState(true);
    
      useEffect(() => {
        console.log("修改DOM", count);
      }, [count]);
    
      useEffect(() => {
        console.log("订阅事件");
      }, []);
    
      useEffect(() => {
        console.log("网络请求");
      }, []);
    
      return (
        <div>
          <h2>MultiEffectHookDemo</h2>
          <h2>{count}</h2>
          <button onClick={e => setCount(count + 1)}>+1</button>
          <h2>{isLogin ? "message": "未登录"}</h2>
          <button onClick={e => setIsLogin(!isLogin)}>登录/注销</button>
        </div>
      )
    }

八、useContext

  1. 在之前的开发中,我们要在组件中使用共享的Context有两种方式:

    类组件可以通过 类名.contextType = MyContext方式,在类中获取context;

    多个Context或者在函数式组件中通过 MyContext.Consumer 方式共享context;
  2. 但是多个Context共享时的方式会存在大量的嵌套:

    Context Hook允许我们通过Hook来直接获取某个Context的值;
  3. 注意事项:

    当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重新渲染,并使用最新传递给 MyContext provider 的 context value 值
  4. 案例:
    export const UserContext = createContext();
    
    export const ThemeContext = createContext();
    <UserContext.Provider value={{ name: 'why', age: 18 }}>
            <ThemeContext.Provider value={{ fontSize: '30px', color: 'red' }}>
              <ContextHookDemo />
            </ThemeContext.Provider>
    </UserContext.Provider>
     
    import React, { useContext } from 'react';
    
    import { UserContext, ThemeContext } from "../App";
    
    export default function ContextHookDemo() {
    
      const user = useContext(UserContext);
      const theme = useContext(ThemeContext);
      console.log(user);
      console.log(theme);
    
      return (
        <div>
          <h2>ContextHookDemo</h2>
        </div>
      )
    }

九、useReducer

  1. 很多人看到useReducer的第一反应应该是redux的某个替代品,其实并不是。
  2. useReducer仅仅是useState的一种替代方案:

    在某些场景下,如果state的处理逻辑比较复杂,我们可以通过useReducer来对其进行拆分;

    或者这次修改的state需要依赖之前的state时,也可以使用;
  3. 数据是不会共享的,它们只是使用了相同的counterReducer的函数而已。
  4. 所以,useReducer只是useState的一种替代品,并不能替代Redux。
  5. 案例:
    home.js
    import React, { useReducer } from 'react';
    import reducer from './reducer';
    
    export default function Home() {
      // const [count, setCount] = useState(0);
    
      const [state, dispatch] = useReducer(reducer, { counter: 0 });
      return (
        <div>
          <h2>Home当前计数: {state.counter}</h2>
          <button onClick={(e) => dispatch({ type: 'increment' })}>+1</button>
          <button onClick={(e) => dispatch({ type: 'decrement' })}>-1</button>
        </div>
      );
    }
    profile.js
    import React, { useReducer } from 'react';
    
    import reducer from './reducer';
    
    export default function Profile() {
      // const [count, setCount] = useState(0);
      const [state, dispatch] = useReducer(reducer, { counter: 0 });
    
      return (
        <div>
          <h2>Profile当前计数: {state.counter}</h2>
          <button onClick={(e) => dispatch({ type: 'increment' })}>+1</button>
          <button onClick={(e) => dispatch({ type: 'decrement' })}>-1</button>
        </div>
      );
    }

    reducer.js
     

    export default function reducer(state, action) {
      switch (action.type) {
        case 'increment':
          return { ...state, counter: state.counter + 1 };
        case 'decrement':
          return { ...state, counter: state.counter - 1 };
        default:
          return state;
      }
    }

     

十、useCallback

  1. useCallback实际的目的是为了进行性能的优化。
  2. 如何进行性能的优化呢?

    useCallback会返回一个函数的 memoized(记忆的) 值;

    在依赖不变的情况下,多次定义的时候,返回的值是相同的;
  3. 案例

    案例一:使用useCallback和不使用useCallback定义一个函数是否会带来性能的优化;
    import React, { useState, useCallback, useMemo } from 'react';
    
    export default function CallbackHookDemo01() {
      const [count, setCount] = useState(0);
    
      const increment1 = () => {
        console.log('执行increment1函数');
        setCount(count + 1);
      };
    
      const increment2 = useCallback(() => {
        console.log('执行increment2函数');
        setCount(count + 1);
      }, [count]);
    
      return (
        <div>
          <h2>CallbackHookDemo01: {count}</h2>
          <button onClick={increment1}>+1</button>
          <button onClick={increment2}>+1</button>
        </div>
      );
    }
    案例二:使用useCallback和不使用useCallback定义一个函数传递给子组件是否会带来性能的优化;
    import React, {useState, useCallback, memo} from 'react';
    
    /**
     * useCallback在什么时候使用?
     * 场景: 在将一个组件中的函数, 传递给子元素进行回调使用时, 使用useCallback对函数进行处理.
     */
    
    const HYButton = memo((props) => {
      console.log("HYButton重新渲染: " + props.title);
      return <button onClick={props.increment}>HYButton +1</button>
    });
    
    export default function CallbackHookDemo02() {
      console.log("CallbackHookDemo02重新渲染");
    
      const [count, setCount] = useState(0);
      const [show, setShow] = useState(true);
    
      const increment1 = () => {
        console.log("执行increment1函数");
        setCount(count + 1);
      }
    
      const increment2 = useCallback(() => {
        console.log("执行increment2函数");
        setCount(count + 1);
      }, [count]);
    
      return (
        <div>
          <h2>CallbackHookDemo01: {count}</h2>
          {/* <button onClick={increment1}>+1</button>
          <button onClick={increment2}>+1</button> */}
          <HYButton title="btn1" increment={increment1}/>
          <HYButton title="btn2" increment={increment2}/>
    
          <button onClick={e => setShow(!show)}>show切换</button>
        </div>
      )
    }
  4. 通常使用useCallback的目的是不希望子组件进行多次渲染,并不是为了函数进行缓存;

十一、useMemo

  1. useMemo实际的目的也是为了进行性能的优化。
  2.  如何进行性能的优化呢?

    useMemo返回的也是一个 memoized(记忆的) 值;

    在依赖不变的情况下,多次定义的时候,返回的值是相同的;
  3. 案例:

    案例一:进行大量的计算操作,是否有必须要每次渲染时都重新计算;
    import React, { useState, useMemo } from 'react';
    
    function calcNumber(count) {
      console.log('calcNumber重新计算');
      let total = 0;
      for (let i = 1; i <= count; i++) {
        total += i;
      }
      return total;
    }
    
    export default function MemoHookDemo01() {
      const [count, setCount] = useState(10);
      const [show, setShow] = useState(true);
    
      const total = calcNumber(count);
      // const total = useMemo(() => {
      //   return calcNumber(count);
      // }, [count]);
    
      return (
        <div>
          <h2>计算数字的和: {total}</h2>
          <button onClick={(e) => setCount(count + 1)}>+1</button>
          <button onClick={(e) => setShow(!show)}>show切换</button>
        </div>
      );
    }


    案例二:对子组件传递相同内容的对象时,使用useMemo进行性能的优化
     
    import React, { useState, memo, useMemo } from 'react';
    
    const HYInfo = memo((props) => {
      console.log("HYInfo重新渲染");
      return <h2>名字: {props.info.name} 年龄: {props.info.age}</h2>
    });
    
    export default function MemoHookDemo02() {
      console.log("MemoHookDemo02重新渲染");
      const [show, setShow] = useState(true);
    
      // const info = { name: "why", age: 18 };
      const info = useMemo(() => {
        return { name: "why", age: 18 };
      }, []);
    
      return (
        <div>
          <HYInfo info={info} />
          <button onClick={e => setShow(!show)}>show切换</button>
        </div>
      )
    }
 

十二、useRef

  1. useRef返回一个ref对象,返回的ref对象再组件的整个生命周期保持不变。
  2. 最常用的ref是两种用法:

    用法一:引入DOM(或者组件,但是需要是class组件)元素;

    用法二:保存一个数据,这个对象在整个生命周期中可以保存不变;
  3. 案例:

    案例一:引用DOM
    import React, { useEffect, useRef } from 'react';
    
    class TestCpn extends React.Component {
      render() {
        return <h2>TestCpn</h2>
      }
    }
    
    function TestCpn2(props) {
      return <h2>TestCpn2</h2>
    }
    
    export default function RefHookDemo01() {
    
      const titleRef = useRef();
      const inputRef = useRef();
      const testRef = useRef();
      const testRef2 = useRef();
    
      function changeDOM() {
        titleRef.current.innerHTML = "Hello World";
        inputRef.current.focus();
        console.log(testRef.current);
        console.log(testRef2.current);
      }
    
      return (
        <div>
          <h2 ref={titleRef}>RefHookDemo01</h2>
          <input ref={inputRef} type="text"/>
          <TestCpn ref={testRef}/>
          <TestCpn2 ref={testRef2}/>
    
          <button onClick={e => changeDOM()}>修改DOM</button>
        </div>
      )
    }
    


    案例二:使用ref保存上一次的某一个值
     
    import React, { useRef, useState, useEffect } from 'react'
    
    export default function RefHookDemo02() {
      const [count, setCount] = useState(0);
    
      const numRef = useRef(count);
    
      useEffect(() => {
        numRef.current = count;
      }, [count])
    
      return (
        <div>
          {/* <h2>numRef中的值: {numRef.current}</h2>
          <h2>count中的值: {count}</h2> */}
          <h2>count上一次的值: {numRef.current}</h2>
          <h2>count这一次的值: {count}</h2>
          <button onClick={e => setCount(count + 10)}>+10</button>
        </div>
      )
    }

十三、useImperativeHandle

  1. ref和forwardRef结合使用:
    import React, { useRef, forwardRef } from 'react';
    
    const HYInput = forwardRef((props, ref) => {
      return <input ref={ref} type="text"/>
    })
    
    export default function ForwardRefDemo() {
      const inputRef = useRef();
    
      return (
        <div>
          <HYInput ref={inputRef}/>
          <button onClick={e => inputRef.current.focus()}>聚焦</button>
        </div>
      )
    }


    通过forwardRef可以将ref转发到子组件;

    子组件拿到父组件中创建的ref,绑定到自己的某一个元素中;
  2. forwardRef的做法本身没有什么问题,但是我们是将子组件的DOM直接暴露给了父组件:

    直接暴露给父组件带来的问题是某些情况的不可控;

    父组件可以拿到DOM后进行任意的操作;

    但是,事实上在上面的案例中,我们只是希望父组件可以操作的focus,其他并不希望它随意操作;
  3. 通过useImperativeHandle可以只暴露固定的操作:
    import React, { useRef, forwardRef, useImperativeHandle } from 'react';
    
    const HYInput = forwardRef((props, ref) => {
      const inputRef = useRef();
    
      useImperativeHandle(ref, () => ({
        focus: () => {
          inputRef.current.focus();
        }
      }), [inputRef])
    
      return <input ref={inputRef} type="text"/>
    })
    
    export default function UseImperativeHandleHookDemo() {
      const inputRef = useRef();
    
      return (
        <div>
          <HYInput ref={inputRef}/>
          <button onClick={e => inputRef.current.focus()}>聚焦</button>
        </div>
      )
    }
    


    通过useImperativeHandle的Hook,将传入的ref和useImperativeHandle第二个参数返回的对象绑定到了一起;

    所以在父组件中,使用 inputRef.current时,实际上使用的是返回的对象;

    比如我调用了 focus函数,甚至可以调用 printHello函数;

十四、useLayoutEffect

  1. useLayoutEffect看起来和useEffect非常的相似,事实上他们也只有一点区别而已:

    useEffect会在渲染的内容更新到DOM上后执行,不会阻塞DOM的更新;

    useLayoutEffect会在渲染的内容更新到DOM上之前执行,会阻塞DOM的更新;
  2. 如果我们希望在某些操作发生之后再更新DOM,那么应该将这个操作放到useLayoutEffect。
  3. 案例: useEffect和useLayoutEffect的对比
     
    import React, { useState, useEffect } from 'react'
    
    export default function EffectCounterDemo() {
      const [count, setCount] = useState(10);
    
      useEffect(() => {
        if (count === 0) {
          setCount(Math.random() + 200)
        }
      }, [count]);
    
      return (
        <div>
          <h2>数字: {count}</h2>
          <button onClick={e => setCount(0)}>修改数字</button>
        </div>
      )
    }
    
    import React, { useState, useEffect, useLayoutEffect } from 'react'
    
    export default function LayoutEffectCounterDemo() {
      const [count, setCount] = useState(10);
    
      useLayoutEffect(() => {
        if (count === 0) {
          setCount(Math.random() + 200)
        }
      }, [count]);
    
      return (
        <div>
          <h2>数字: {count}</h2>
          <button onClick={e => setCount(0)}>修改数字</button>
        </div>
      )
    }

    可以看到使用useEffect,当点击按钮时,会先执行setCount(0),界面重新渲染,渲染完成后,执行useEffect,再次更改count,界面再次渲染,在界面上会看到数字闪烁一下,而使用useLayoutEffect,点击按钮时setCount只会执行一次,不会出现数字闪烁一下问题。

十五、自定义Hook

  1. 自定义Hook本质上只是一种函数代码逻辑的抽取,严格意义上来说,它本身并不算React的特性。
  2. 案例:所有的组件在创建和销毁时都进行打印
    function useLoggingLife(name) {
      useEffect(() => {
        console.log(`${name}组件被创建出来了`);
    
        return () => {
          console.log(`${name}组件被销毁掉了`);
        }
      }, []);
    }


    组件被创建:打印 组件被创建了;

    组件被销毁:打印 组件被销毁了;

1、Context的共享

export const UserContext = createContext();
export const TokenContext = createContext();
import { useContext } from "react";
import { UserContext, TokenContext } from "../App";

function useUserContext() {
  const user = useContext(UserContext);
  const token = useContext(TokenContext);

  return [user, token];
}

export default useUserContext;

2、获取鼠标滚动位置

import { useState, useEffect } from 'react';

function useScrollPosition() {
  const [scrollPosition, setScrollPosition] = useState(0);

  useEffect(() => {
    const handleScroll = () => {
      setScrollPosition(window.scrollY);
    }
    document.addEventListener("scroll", handleScroll);

    return () => {
      document.removeEventListener("scroll", handleScroll)
    }
  }, []);

  return scrollPosition;
}

export default useScrollPosition;

3、localStorage数据存储

import {useState, useEffect} from 'react';

function useLocalStorage(key) {
  const [name, setName] = useState(() => {
    const name = JSON.parse(window.localStorage.getItem(key));
    return name;
  });

  useEffect(() => {
    window.localStorage.setItem(key, JSON.stringify(name));
  }, [name]);

  return [name, setName];
}

export default useLocalStorage;

十六、useState源码分析

 

十七、redux hooks

  1. 在之前的redux开发中,为了让组件和redux结合起来,我们使用了react-redux中的connect:

    但是这种方式必须使用高阶函数结合返回的高阶组件;

    并且必须编写:mapStateToProps和 mapDispatchToProps映射的函数;
  2. 在Redux7.1开始,提供了Hook的方式,我们再也不需要编写connect以及对应的映射函数了
  3. useSelector的作用是将state映射到组件中:

    参数一:将state映射到需要的数据中;

    参数二:可以进行比较来决定是否组件重新渲染;(后续讲解)
  4. useSelector默认会比较我们返回的两个对象是否相等;

    如何比较呢? const refEquality = (a, b) => a === b;

    也就是我们必须返回两个完全相等的对象才可以不引起重新渲染;
  5. useDispatch非常简单,就是直接获取dispatch函数,之后在组件中直接使用即可;
  6. 我们还可以通过useStore来获取当前的store对象;
 
 
 
 
 
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

React之state、hooks性能分析 的相关文章

  • css in js开发利器 - styled-components(样式组件)

    styled components 是一个常见的 css in js 类库 和所有同类型的类库一样 通过 js 赋能解决了原生 css 所不具备的能力 比如变量 循环 函数等 注意 有时候 React 版本和 styled componen
  • 对Fiber架构的理解?解决了什么问题?

    一 问题 JavaScript引擎和页面渲染引擎两个线程是互斥的 当其中一个线程执行时 另一个线程只能挂起等待 如果 JavaScript 线程长时间地占用了主线程 那么渲染层面的更新就不得不长时间地等待 界面长时间不更新 会导致页面响应度
  • 对useReducer的理解

    useReducer是React提供的一个高级Hook 它不像useEffect useState useRef等必须hook一样 没有它我们也可以正常完成需求的开发 但useReducer可以使我们的代码具有更好的可读性 可维护性 可预测
  • state和props的区别__react

    首先说明 state和props是每个组件都有的 其次 state可变 但props不可变 这是官网给出的说法 但实操过程中 state的确可变 但props也可以变 是不是fb搞错了 当然不是 这里的可变与不可变 说的是改变后 是否会重新
  • ant design pro v5 配置拦截器,header

    ant design pro v5 配置拦截器 header 1 资料文档 https umijs org zh CN plugins plugin request requestinterceptors 2 编写app tsx 我这里是自
  • Ionic3开发教程 - 开发(2)

    Ionic3开发系列教程Ionic3开发教程 环境准备 1 Ionic3开发教程 开发 2 Ionic3开发教程 发布Android版本 3 Ionic3开发教程 发布IOS版本 4 Ionic3开发教程 更新 5 本文中介绍的Ionic3
  • chrome浏览器安装redux-devtools调试工具

    chrome浏览器安装redux devtools调试工具 1 点击进入https www chromefor com 2 在搜索框搜索redux 3 找到最新版本 Redux DevTools v2 17 0 进行下载 4 选择下载线路
  • react组件状态同步-状态提升

    假设定义组件TemperatureInputSon import React from react class TemperatureInputSon extends React Component constructor props su
  • React中使用if else 条件判断

    在react中用jsx渲染dom的时候经常会遇到if条件判断 然而在jsx中竟是不允许if条件判断的 以下有几种判断方式 可以根据自己的应用场景 挑选适合的 方案一 class HelloMessage extends React Comp
  • react之纯函数、函数组件、类组件、纯组件

    一 纯函数 Pure Function 定义 一个函数的返回结果只依赖于它的参数 并且在执行的过程中没有副作用 我们就把该函数称作纯函数 特点 1 函数的返回结果只依赖与它的参数 同一个输入只能有同一个输出 let foo a b gt a
  • react和react jsx基础

    本文是个人学习笔记 例子都是来自React Native官网 之前不是做前端的 没有使用过react 要学习react native做混合开发 react 包括react jsx还是得补补 react和react jsx react是一个j
  • React(一):React的设计哲学 - 简单之美

    React 一 React的设计哲学 简单之美 React 二 React开发神器Webpack React 三 理解JSX和组件 React 四 虚拟DOM Diff算法解析 React 五 使用Flux搭建React应用程序架构 Rea
  • React核心概念:状态提升

    上一节 表单 下一节 组合vs继承 React核心概念 状态提升 引言 添加第二个输入框 编写转换函数 状态提升 经验总结 引言 很多情况下我们使用的多个组件需要对同一个数据做出对应的反应 在这里我们推荐把这个共享的状态提升到距离这些组件最
  • React官方文档--Lifting State Up

    Lifting State Up 如果 几个组件需要同时影响并且修改同一个数据 我们建议将这个共享状态进行提升 给他们最近的共同祖先 在这个部分 我们将要创建一个温度计算器来判断水会不会在给定温度下沸腾 我们将从一个叫做BoilingVer
  • React、Vue2.x、Vue3.0的diff算法

    前言 本文章不讲解 vDom 实现 mount 挂载 以及 render 函数 只讨论三种 diff 算法 VNode 类型不考虑 component functional component Fragment Teleport 只考虑 E
  • ant design pro 代码学习(七) ----- 组件封装(登录模块)

    以登录模块为例 对ant design pro的组件封装进行相关分析 登录模块包含基础组件的封装 组件按模块划分 同类组件通过配置文件生成 跨层级组件直接数据通信等 相对来说还是具有一定的代表性 1 登录模块流程图 首先 全局了解一下登录模
  • vue发展历史简介

    基本介绍 Vue 是一套用于构建用户界面的 渐进式框架 与其它大型框架不同的是 Vue 被设计为可以自底向上逐层应用 最初它不过是个人项目 时至今日 已成为全世界三大前端框架之一 github 上拥有 17 8万 Star 领先于 Reac
  • error Missing “key“ prop for element in array react/jsx-key

    react遇到一个奇怪的问题 error Missing key prop for element in array react jsx key 检查了jsx中使用map的 都定义了key div otherList map item an
  • reactJS 干货(reactjs 史上最详细的解析干货)

    一 State和 Props state是状态机 应该包括 那些可能被组件的事件处理器改变并触发用户界面更新的数据 譬如需要对用户输入 服务器请求或者时间变化等作出响应 不应该包括 计算所得数据 React组件 在render 里使用pro
  • React Jsx转换成真实DOM过程?

    面试官 说说React Jsx转换成真实DOM过程 一 是什么 react 通过将组件编写的 JSX 映射到屏幕 以及组件中的状态发生了变化之后 React 会将这些 变化 更新到屏幕上 在前面文章了解中 JSX 通过 babel 最终转化

随机推荐

  • Qt动画框架设计飞入-消失特效

    用Qt动画框架设计飞入 消失特效 Qt动画框架很强大 只要你想得到 它就有可能帮你实现 这一次我将抽取上一个演示程序的部分来进行介绍 这一部分我命名为 飞入 消失 特效 主要用在文字的显示方面 从这点上说很像PowerPoint上面特效的一
  • Spark常用参数解释

    Spark的默认配置文件位于堡垒机上的这个位置 SPARK CONF DIR spark defaults conf 用户可以自行查看和理解 需要注意的是 默认值优先级最低 用户如果提交任务时或者代码里明确指定配置 则以用户配置为先 用户再
  • python stats_python statsmodel的使用

    1 Pandas Python Data Analysis Library 或 pandas 是基于NumPy 的一种工具 相当于这是Python官方自己的一套库 statsmodel是基于Pandas开发的一套库 用于一些描述统计 统计模
  • MySQL 数据库备份(包含存储过程) 和 还原数据库

    备份数据库 使用命令 mysqldump u用户名 p密码 R 数据库名字 gt t sql sql R 表示 备份数据库时 同时也备份存储过程 还原数据库 运用了一个比较 笨 的方法 在MySQL里面手动新建一个数据库 然后把t sql
  • 部署stable diffusion时踩过的坑

    一个月前开始接触AI绘画 几天前开始学习stable diffusion 由于对自身电脑配置的信心不大 因此开始的时候使用的google免费的15G云盘空间进行云部署 但是15G内存对于想要生成更多的图片的人来说不是很够的 因为在使用过程中
  • 同事都在偷偷用的Python接单平台竟然是这8个!!轻松让你月入上w!

    一 Python爬虫学到怎么样可以接单 1 基础简单回顾 想要上手爬虫 基本知识和工具的熟练使用是必须要具备的 首先Python的一些语言基础肯定要有 爬虫大部分是用python写的 基本的语法 数据结构 函数等要熟练 比如 List di
  • vscode: Downloading package ‘C/C++ language components (Linux / x86_64)‘ Failed.

    使用vscode远程连接docker容器 进入容器后报错 Updating C C dependencies Downloading package C C language components Linux x86 64 Failed R
  • extern const static

    1 基本解释 extern可以置于变量或者函数前 以标示变量或者函数的定义在别的文件中 提示编译器遇到此变量和函数时在其他模块中寻找其定义 此外extern也可用来进行链接指定 也就是说extern有两个作用 第一个 当它与 C 一起连用时
  • 2021-07-29

    git和GitHub的搭配使用1 作为一个小白 一直觉得GitHub是个程序员大佬玩转的东西 最近在发现了宝贝教程https www bilibili com video BV1db4y1d79C spm id from 333 788 b
  • Excalidraw本地化部署

    1 Excalidraw介绍 Excalidraw是一个开源 小巧易用的手写风格的框图画板软件 excalidraw官网地址 https excalidraw com 2 Excalidraw本地化安装 git方式 2 1安装部署 在ter
  • shell case 分支选择

    转自 http hlee iteye com blog 577628 case和select结构在技术上说并不是循环 因为它们并不对可执行代码块进行迭代 但是和循环相似的是 它们也依靠在代码块顶部或底部的条件判断来决定程序的分支 在代码块中
  • STM32单片机的IIC硬件编程---查询等待方式

    IIC器件是一种介于高速和低速之间的嵌入式外围设备 其实总体来说 它的速度算是比较慢的 通常情况下 速度慢的器件意味着更多的等待 这对于精益求精的嵌入式工程师来说 简直就是一个恶梦 低速器件的存取数据实在是太浪费资源 如何面对这种低速设备
  • 【日常】DBeaver中sql连接,局域网状态下

    投身外包大军 项目组使用内网 不能自己下软件 不能自己带U盘 上头的很 使用的DBeaver进行数据库的使用 碰到了sql连接的问题 记录一下 不得不说这个软件的图标有点可爱 一个小河狸 内网连接 不能联网 所以一般配套会给一个sql co
  • 父进程等待子进程终止 wait, WIFEXITED, WEXITSTATUS

    wait 的函数原型是 include
  • jqGrid 编辑完数据后能返回到当前位置的方法

    jqGrid 是一个js的jquery组件 虽然不轻便 但功能还是蛮强大的 也比较方便使用 在数据加载后 经常需要对其中的记录进行编辑 修改完后再返回时需要看到修改后的数据 一般采取重新加载的方法reloadGrid 但问题是列表中的数据因
  • STM8自学入门方向

    我还是我 今年计划自学学习STM8和汇编基础 STM8花了半个月 学的一点皮毛 对芯片有一定的了解了 学完后 发现可以拿到的资源远远没有32多 学习了内部大部分常用资源的应用 IO操作 定时器 IO中断 RS232 IIC 后面会发布我的总
  • 大数据时代下的个人知识管理

    前言 说到个人知识管理 在之前通过网络查询了一些资料 定义看起来让人蠢蠢欲动 作用是能快速找到自己收藏的文档 每个人或多或少都必须的有一些文件管理的习惯 管理就是一种习惯 利用专业的软件可以更容易的养成个人知识管理的习惯 当不小心清空了自己
  • c++双向列表释放_Python 列表List常见操作和错误总结

    一 列表的输入 即从控制台读取输入 然后创建列表 1 一维列表创建常见的方法有 当然 可以进一步简化成下面这样 其中第二句 在列表里用到了列表解析式 这是非常Pythonic的写法 酷炫 2 二维列表的输入和创建 二维列表复杂一些 可以以矩
  • Quartz-Spring[一]之MethodInvokingJobDetailFactoryBean配置任务

    Spring中使用Quartz的3种方法 MethodInvokingJobDetailFactoryBean implements Job extends QuartzJobBean 动态启动 暂定 添加 删除定时功能 可传参数 Quar
  • React之state、hooks性能分析

    目录 一 state 1 为什么使用setState 2 setState异步更新 3 如何获取异步的结果 4 setState一定是异步吗 5 源码分析 6 数据的合并 7 多个state的合并 二 为什么需要Hook 三 Class组件