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性能分析 的相关文章

  • React中的“计算属性”

    React中的 计算属性 相信许多学习过vue的小伙伴对计算属性都不陌生吧 计算属性能帮我们数据进行一些计算操作 计算属性是依赖于data里面的数据的 在vue中只要计算属性依赖的data值发生改变 则计算属性就会调用 那React中也有计
  • Antd DatePicker 设置默认值报clone.weekday is not a function

    代码 dayjs版本1 11 7 页面 当点击页面日期框会报clone weekday is not a function 解决方法 在jsx文件中添加如下js import dayjs from dayjs import advanced
  • 在react项目中,使用craco插件进行mobx配置解决方案

    在使用react项目中 不可避免的要使用蚂蚁金服出品的ant desgin前端UI组件 ant desgin推荐使用 craco 一个对 create react app 进行自定义配置的社区解决方案 对 create react app
  • react项目中使用react-dnd实现列表的拖拽排序

    现在有一个新需求就是需要对一个列表 实现拖拽排序的功能 要实现的效果如下图 可以通过 react dnd 或者 react beautiful dnd 两种方式实现 今天先讲下使用react dnd是如何实现的 github地址 https
  • React的State Hook用法详解

    一 State Hook是啥 State Hook 就是指 useState 这个特殊函数 让你不用编写class 就可以使用state特性 换言之就是让 函数组件 拥有 state 特性 对数据进行动态更新 二 class中的state
  • 对 React Hook的闭包陷阱的理解,有哪些解决方案?

    hooks中 奇怪 其实符合逻辑 的 闭包陷阱 的场景 同时 在许多 react hooks 的文章里 也能看到 useRef 的身影 那么为什么使用 useRef 又能摆脱 这个 闭包陷阱 搞清楚这些问题 将能较大的提升对 react h
  • 如何在 Ubuntu 20.04 上使用 React 前端设置 Ruby on Rails v7 项目

    作者选择了电子前沿基金会接受捐赠作为为捐款而写程序 介绍 红宝石 on Rails是一个流行的服务器端 Web 应用程序框架 它为当今网络上存在的许多流行应用程序提供支持 例如GitHub Basecamp 声云 Airbnb and Tw
  • styled-components 的用法

    用于给标签或组件添加样式 给标签或组件添加样式 import styled from styled components styled button 给button标签添加样式 const Button styled button back
  • 配置使用Eslint的时候 版本错误 "eslint": "5.6.0" a different version of eslint was detected higher up in the tr

    1 如果你也遇到下面的问题 你可以 按照命令行提示的那样 下面这四步完成的一般就可以了 但是不排除你在运行的时候忘记下载某些依赖 1 删除 package lock json 不是package json 你可以选择 yarn lock 或
  • react基础--组件通讯:props基础、子传父、父传子、兄弟组件通讯、context跨级组件、props进阶

    目录 一 props基础 1 1 概述 1 2 函数组件通讯 1 2 1 基本用法 1 2 1 对象数据传递 1 3 类组件通讯 1 4 props的特点 二 组件通讯三种方式 2 1 父传子 2 2 子传父 2 3 兄弟组件通讯 三 co
  • chrome浏览器安装redux-devtools调试工具

    chrome浏览器安装redux devtools调试工具 1 点击进入https www chromefor com 2 在搜索框搜索redux 3 找到最新版本 Redux DevTools v2 17 0 进行下载 4 选择下载线路
  • 【Ant Design】Form.Item创建自定义表单

    一 概述 Antd是一个非常强大的UI组件库 里面的Form表单组件也基本能满足我们大多数场景 但是也有需要自定义表单的场景 Vue2里我们使用v model 结合子组件的model属性 来实现自定义组件的双向绑定 Vue3里我们使用v m
  • Vite搭建react+ts项目

    创建一个react项目 首先需要打开终端 进行vite的引入 yarn create vite 使用react模板创建项目 yarn create vite react test template react cd react test y
  • 一文揭秘饿了么跨端技术的演进、实践与落地

    本文会先带领大家一起简单回顾下跨端技术背景与演进历程与在这一波儿接着一波儿的跨端浪潮中的饿了么跨端现状 以及在这个背景下 相较于业界基于 React Vue 研发习惯出发的各种跨端方案 饿了么为什么会选择走另外一条路 这个过程中我们的一些思
  • 三分钟实现一个react-router-dom5.0的路由拦截(导航守卫)

    不同于vue 通过在路由里设置meta元字符实现路由拦截 在使用 Vue 框架提供了路由守卫功能 用来在进入某个路有前进行一些校验工作 如果校验失败 就跳转到 404 或者登陆页面 比如 Vue 中的 beforeEnter 函数 rout
  • 关于Vue.js和React.js,听听国外的开发者怎么说?

    VueJS 与 ReactJS 到底怎么样如何 听听别人怎么说 使用所有新的库和框架 很难跟上所有这些库和框架 也就是说 这就需要您决定哪些是值得花时间的 让我们看看人们说什么 和Vue JS一起工作是很愉快的 我发现学习曲线很浅 然而 这
  • React-(4)React中的事件绑定

    React中的事件绑定 在 React 组件中 每个方法的上下文都会指向该组件的实例 即自动绑定 this 为当前组件 而且 React 还会对这种引用进行缓存 以达到 CPU 和内存的最优化 在使用 ES6 classes 或者纯函数时
  • react之纯函数、函数组件、类组件、纯组件

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

    子组件中 const child forwardRef props ref gt useImperativeHandle ref gt 这里面的方法是暴露给父组件的 test console log 我是组件里的test方法 test2 t
  • 值得收藏的UmiJS 教程

    点击上方关注 前端技术江湖 一起学习 天天进步 前言 网上的umi教程是真的少 很多人都只写了一点点 很多水文 所以打算自己写一篇 自己搭建umi 并封装了一下常用的功能 并用到公司实际项目中 umi介绍 Umi 是什么 Umi 中文可发音

随机推荐

  • 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组件