iis服务器助手广告广告
返回顶部
首页 > 资讯 > 前端开发 > JavaScript >React Hooks常用场景的使用(小结)
  • 551
分享到

React Hooks常用场景的使用(小结)

2024-04-02 19:04:59 551人浏览 薄情痞子
摘要

目录一、State Hook1、基础用法2、更新3、实现合并4、惰性初始化 state5、一些重点二、Effect Hook1、基础用法2、清除操作3、执行时期4、性能优化5、模拟

前言

React 在 v16.8 的版本中推出了 React Hooks 新特性。在我看来,使用 React Hooks 相比于从前的类组件有以下几点好处:

  • 代码可读性更强,原本同一块功能的代码逻辑被拆分在了不同的生命周期函数中,容易使开发者不利于维护和迭代,通过 React Hooks 可以将功能代码聚合,方便阅读维护;
  • 组件树层级变浅,在原本的代码中,我们经常使用 HOC/render props 等方式来复用组件的状态,增强功能等,无疑增加了组件树层数及渲染,而在 React Hooks 中,这些功能都可以通过强大的自定义的 Hooks 来实现;

关于这方面的文章,我们根据使用场景分别进行举例说明,帮助你认识理解并可以熟练运用 React Hooks 大部分特性。

博客 GitHub地址为:https://github.com/fengshi123/blog

一、State Hook

1、基础用法


function State(){
  const [count, setCount] = useState(0);
  return (
      <div>
          <p>You clicked {count} times</p>
          <button onClick={() => setCount(count + 1)}>
              Click me
          </button>
      </div>
  )
}

2、更新

更新分为以下两种方式,即直接更新和函数式更新,其应用场景的区分点在于:

  • 直接更新不依赖于旧 state 的值;
  • 函数式更新依赖于旧 state 的值;

// 直接更新
setState(newCount);

// 函数式更新
setState(prevCount => prevCount - 1);

3、实现合并

与 class 组件中的 setState 方法不同,useState 不会自动合并更新对象,而是直接替换它。我们可以用函数式的 setState 结合展开运算符来达到合并更新对象的效果。


setState(prevState => {
  // 也可以使用 Object.assign
  return {...prevState, ...updatedValues};
});

4、惰性初始化 state

initialState 参数只会在组件的初始渲染中起作用,后续渲染时会被忽略。其应用场景在于:创建初始 state 很昂贵时,例如需要通过复杂计算获得;那么则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用:


const [state, setState] = useState(() => {
  const initialState = someExpensiveComputation(props);
  return initialState;
});

5、一些重点

(1)不像 class 中的 this.setState ,Hook 更新 state 变量总是替换它而不是合并它;
(2)推荐使用多个 state 变量,而不是单个 state 变量,因为 state 的替换逻辑而不是合并逻辑,并且利于后续的相关 state 逻辑抽离;
(3)调用 State Hook 的更新函数并传入当前的 state 时,React 将跳过子组件的渲染及 effect 的执行。(React 使用 Object.is 比较算法 来比较 state。)

二、Effect Hook

1、基础用法


function Effect(){
  const [count, setCount] = useState(0);
  useEffect(() => {
    console.log(`You clicked ${count} times`);
  });

  return (
      <div>
          <p>You clicked {count} times</p>
          <button onClick={() => setCount(count + 1)}>
              Click me
          </button>
      </div>
  )
}

2、清除操作

为防止内存泄漏,清除函数会在组件卸载前执行;如果组件多次渲染(通常如此),则在执行下一个 effect 之前,上一个 effect 就已被清除,即先执行上一个 effect 中 return 的函数,然后再执行本 effect 中非 return 的函数。


useEffect(() => {
  const subscription = props.source.subscribe();
  return () => {
    // 清除订阅
    subscription.unsubscribe();
  };
});

3、执行时期

与 componentDidMount 或 componentDidUpdate 不同,使用 useEffect 调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快;(componentDidMount 或 componentDidUpdate 会阻塞浏览器更新屏幕)

4、性能优化

默认情况下,React 会每次等待浏览器完成画面渲染之后延迟调用 effect;但是如果某些特定值在两次重渲染之间没有发生变化,你可以通知 React 跳过对 effect 的调用,只要传递数组作为 useEffect 的第二个可选参数即可:如下所示,如果 count 值两次渲染之间没有发生变化,那么第二次渲染后就会跳过 effect 的调用;


useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新

5、模拟 componentDidMount

如果想只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([ ])作为第二个参数,如下所示,原理跟第 4 点性能优化讲述的一样;


useEffect(() => {
  .....
}, []);

6、最佳实践

要记住 effect 外部的函数使用了哪些 props 和 state 很难,这也是为什么 通常你会想要在 effect 内部 去声明它所需要的函数。


// bad,不推荐
function Example({ someProp }) {
  function doSomething() {
    console.log(someProp);
  }

  useEffect(() => {
    doSomething();
  }, []); // 🔴 这样不安全(它调用的 `doSomething` 函数使用了 `someProp`)
}

// Good,推荐
function Example({ someProp }) {
  useEffect(() => {
    function doSomething() {
      console.log(someProp);
    }

    doSomething();
  }, [someProp]); // ✅ 安全(我们的 effect 仅用到了 `someProp`)
}

如果处于某些原因你无法把一个函数移动到 effect 内部,还有一些其他办法:

  • 你可以尝试把那个函数移动到你的组件之外。那样一来,这个函数就肯定不会依赖任何 props 或 state,并且也不用出现在依赖列表中了;
  • 万不得已的情况下,你可以 把函数加入 effect 的依赖但 把它的定义包裹 进 useCallback Hook。这就确保了它不随渲染而改变,除非它自身的依赖发生了改变;

推荐启用 eslint-plugin-react-hooks 中的 exhaustive-deps 规则,此规则会在添加错误依赖时发出警告并给出修复建议 ;


// 1、安装插件
npm i eslint-plugin-react-hooks --save-dev

// 2、eslint 配置
{
  "plugins": [
    // ...
    "react-hooks"
  ],
  "rules": {
    // ...
    "react-hooks/rules-of-hooks": "error",
    "react-hooks/exhaustive-deps": "warn"
  }
}

7、一些重点

(1)可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate和 componentWillUnmount这三个函数的组合;
(2)在 React 的 class 组件中,render 函数是不应该有任何副作用的; 一般来说,在这里执行操作太早了,我们基本上都希望在 React 更新 DOM 之后才执行我们的操作。

三、useContext

用来处理多层级传递数据的方式,在以前组件树中,跨层级祖先组件想要给孙子组件传递数据的时候,除了一层层 props 往下透传之外,我们还可以使用 React Context api 来帮我们做这件事。使用例子如下所示
(1)使用 React Context API,在组件外部建立一个 Context


import React from 'react';
const ThemeContext = React.createContext(0);
export default ThemeContext;

(2)使用 Context.Provider提供了一个 Context 对象,这个对象可以被子组件共享


import React, { useState } from 'react';
import ThemeContext from './ThemeContext';
import ContextComponent1 from './ContextComponent1';

function ContextPage () {
  const [count, setCount] = useState(1);
  return (
    <div className="App">
      <ThemeContext.Provider value={count}>
        <ContextComponent1 />
      </ThemeContext.Provider>
      <button onClick={() => setCount(count + 1)}>
              Click me
      </button>
    </div>
  );
}

export default ContextPage;

(3)useContext()钩子函数用来引入 Context 对象,并且获取到它的值


// 子组件,在子组件中使用孙组件
import React from 'react';
import ContextComponent2 from './ContextComponent2';
function ContextComponent () {
  return (
    <ContextComponent2 />
  );
}
export default ContextComponent;


// 孙组件,在孙组件中使用 Context 对象值
import React, { useContext } from 'react';
import ThemeContext from './ThemeContext';
function ContextComponent () {
  const value = useContext(ThemeContext);
  return (
    <div>useContext:{value}</div>
  );
}
export default ContextComponent;

四、useReducer

1、基础用法

比 useState 更适用的场景:例如 state 逻辑处理较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等;例子如下所示


import React, { useReducer } from 'react';
interface stateType {
  count: number
}
interface actionType {
  type: string
}
const initialState = { count: 0 };
const reducer = (state:stateType, action:actionType) => {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
};
const UseReducer = () => {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div className="App">
      <div>useReducer Count:{state.count}</div>
      <button onClick={() => { dispatch({ type: 'decrement' }); }}>useReducer 减少</button>
      <button onClick={() => { dispatch({ type: 'increment' }); }}>useReducer 增加</button>
    </div>
  );
};

export default UseReducer;

2、惰性初始化 state


interface stateType {
  count: number
}
interface actionType {
  type: string,
  paylod?: number
}
const initCount =0 
const init = (initCount:number)=>{
  return {count:initCount}
}
const reducer = (state:stateType, action:actionType)=>{
  switch(action.type){
    case 'increment':
      return {count: state.count + 1}
    case 'decrement':
      return {count: state.count - 1}
    case 'reset':
      return init(action.paylod || 0)
    default:
      throw new Error();
  }
}
const UseReducer = () => {
  const [state, dispatch] = useReducer(reducer,initCount,init)

  return (
    <div className="App">
      <div>useReducer Count:{state.count}</div>
      <button onClick={()=>{dispatch({type:'decrement'})}}>useReducer 减少</button>
      <button onClick={()=>{dispatch({type:'increment'})}}>useReducer 增加</button>
      <button onClick={()=>{dispatch({type:'reset',paylod:10 })}}>useReducer 增加</button>
    </div>
  );
}
export default UseReducer;

五、Memo

如下所示,当父组件重新渲染时,子组件也会重新渲染,即使子组件的 props 和 state 都没有改变
import React, { memo, useState } from 'react';


// 子组件
const ChildComp = () => {
  console.log('ChildComp...');
  return (<div>ChildComp...</div>);
};

// 父组件
const Parent = () => {
  const [count, setCount] = useState(0);

  return (
    <div className="App">
      <div>hello world {count}</div>
      <div onClick={() => { setCount(count => count + 1); }}>点击增加</div>
      <ChildComp/>
    </div>
  );
};

export default Parent;

改进:我们可以使用 memo 包一层,就能解决上面的问题;但是仅仅解决父组件没有传参给子组件的情况以及父组件传简单类型的参数给子组件的情况(例如 string、number、boolean等);如果有传复杂属性应该使用 useCallback(回调事件)或者 useMemo(复杂属性)


// 子组件
const ChildComp = () => {
  console.log('ChildComp...');
  return (<div>ChildComp...</div>);
};

const MemoChildComp = memo(ChildComp);

六、useMemo

假设以下场景,父组件在调用子组件时传递 info 对象属性,点击父组件按钮时,发现控制台会打印出子组件被渲染的信息。


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

// 子组件
const ChildComp = (info:{info:{name: string, age: number}}) => {
  console.log('ChildComp...');
  return (<div>ChildComp...</div>);
};

const MemoChildComp = memo(ChildComp);

// 父组件
const Parent = () => {
  const [count, setCount] = useState(0);
  const [name] = useState('jack');
  const [age] = useState(11);
  const info = { name, age };

  return (
    <div className="App">
      <div>hello world {count}</div>
      <div onClick={() => { setCount(count => count + 1); }}>点击增加</div>
      <MemoChildComp info={info}/>
    </div>
  );
};

export default Parent;

分析原因:

  • 点击父组件按钮,触发父组件重新渲染;
  • 父组件渲染,const info = { name, age } 一行会重新生成一个新对象,导致传递给子组件的 info 属性值变化,进而导致子组件重新渲染。

解决:

使用 useMemo 将对象属性包一层,useMemo 有两个参数:

  • 第一个参数是个函数,返回的对象指向同一个引用,不会创建新对象;
  • 第二个参数是个数组,只有数组中的变量改变时,第一个参数的函数才会返回一个新的对象。

import React, { memo, useMemo, useState } from 'react';

// 子组件
const ChildComp = (info:{info:{name: string, age: number}}) => {
  console.log('ChildComp...');
  return (<div>ChildComp...</div>);
};

const MemoChildComp = memo(ChildComp);

// 父组件
const Parent = () => {
  const [count, setCount] = useState(0);
  const [name] = useState('jack');
  const [age] = useState(11);
  
  // 使用 useMemo 将对象属性包一层
  const info = useMemo(() => ({ name, age }), [name, age]);

  return (
    <div className="App">
      <div>hello world {count}</div>
      <div onClick={() => { setCount(count => count + 1); }}>点击增加</div>
      <MemoChildComp info={info}/>
    </div>
  );
};

export default Parent;

七 、useCallback

接着第六章节的例子,假设需要将事件传给子组件,如下所示,当点击父组件按钮时,发现控制台会打印出子组件被渲染的信息,说明子组件又被重新渲染了。


import React, { memo, useMemo, useState } from 'react';

// 子组件
const ChildComp = (props:any) => {
  console.log('ChildComp...');
  return (<div>ChildComp...</div>);
};

const MemoChildComp = memo(ChildComp);

// 父组件
const Parent = () => {
  const [count, setCount] = useState(0);
  const [name] = useState('jack');
  const [age] = useState(11);
  const info = useMemo(() => ({ name, age }), [name, age]);
  const changeName = () => {
    console.log('输出名称...');
  };

  return (
    <div className="App">
      <div>hello world {count}</div>
      <div onClick={() => { setCount(count => count + 1); }}>点击增加</div>
      <MemoChildComp info={info} changeName={changeName}/>
    </div>
  );
};

export default Parent;

分析下原因:

  • 点击父组件按钮,改变了父组件中 count 变量值(父组件的 state 值),进而导致父组件重新渲染;
  • 父组件重新渲染时,会重新创建 changeName 函数,即传给子组件的 changeName 属性发生了变化,导致子组件渲染;

解决:
修改父组件的 changeName 方法,用 useCallback 钩子函数包裹一层, useCallback 参数与 useMemo 类似


import React, { memo, useCallback, useMemo, useState } from 'react';

// 子组件
const ChildComp = (props:any) => {
  console.log('ChildComp...');
  return (<div>ChildComp...</div>);
};

const MemoChildComp = memo(ChildComp);

// 父组件
const Parent = () => {
  const [count, setCount] = useState(0);
  const [name] = useState('jack');
  const [age] = useState(11);
  const info = useMemo(() => ({ name, age }), [name, age]);
  const changeName = useCallback(() => {
    console.log('输出名称...');
  }, []);

  return (
    <div className="App">
      <div>hello world {count}</div>
      <div onClick={() => { setCount(count => count + 1); }}>点击增加</div>
      <MemoChildComp info={info} changeName={changeName}/>
    </div>
  );
};

export default Parent;

八、useRef

以下分别介绍 useRef 的两个使用场景:

1、指向 dom 元素

如下所示,使用 useRef 创建的变量指向一个 input 元素,并在页面渲染后使 input 聚焦


import React, { useRef, useEffect } from 'react';
const Page1 = () => {
  const myRef = useRef<htmlInputElement>(null);
  useEffect(() => {
    myRef?.current?.focus();
  });
  return (
    <div>
      <span>UseRef:</span>
      <input ref={myRef} type="text"/>
    </div>
  );
};

export default Page1;

2、存放变量

useRef 在 react hook 中的作用, 正如官网说的, 它像一个变量, 类似于 this , 它就像一个盒子, 你可以存放任何东西. createRef 每次渲染都会返回一个新的引用,而 useRef 每次都会返回相同的引用,如下例子所示:


import React, { useRef, useEffect, useState } from 'react';
const Page1 = () => {
    const myRef2 = useRef(0);
    const [count, setCount] = useState(0)
    useEffect(()=>{
      myRef2.current = count;
    });
    function handleClick(){
      setTimeout(()=>{
        console.log(count); // 3
        console.log(myRef2.current); // 6
      },3000)
    }
    return (
    <div>
      <div onClick={()=> setCount(count+1)}>点击count</div>
      <div onClick={()=> handleClick()}>查看</div>
    </div>
    );
}

export default Page1;

九、useImperativeHandle

使用场景:通过 ref 获取到的是整个 dom 节点,通过 useImperativeHandle 可以控制只暴露一部分方法和属性,而不是整个 dom 节点。

十、useLayoutEffect

其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect,这里不再举例。

useLayoutEffect 和平常写的 Class 组件的 componentDidMount 和 componentDidUpdate 同时执行;
useEffect 会在本次更新完成后,也就是第 1 点的方法执行完成后,再开启一次任务调度,在下次任务调度中执行 useEffect;

总结

关于这方面的文章,我们根据使用场景分别进行举例说明,希望有帮助到你认识理解并可以熟练运用 React Hooks 大部分特性。

到此这篇关于React Hooks常用场景的使用(小结)的文章就介绍到这了,更多相关React Hooks常用场景内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: React Hooks常用场景的使用(小结)

本文链接: https://www.lsjlt.com/news/123458.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • React Hooks常用场景的使用(小结)
    目录一、State Hook1、基础用法2、更新3、实现合并4、惰性初始化 state5、一些重点二、Effect Hook1、基础用法2、清除操作3、执行时期4、性能优化5、模拟 ...
    99+
    2024-04-02
  • React Hooks使用常见的坑
    React Hooks 是 React 16.8 引入的新特性,允许我们在不使用 Class 的前提下使用 state 和其他特性。React Hooks 要解决的问题是状态共享,是...
    99+
    2024-04-02
  • React useMemo和useCallback的使用场景
    目录useMemouseCallback useMemo 我们知道当父组件发生重新渲染时,其所有(状态、局部变量等)都是新的。一旦子组件依赖于父组件的某一个对象变量,那么无论对象是否...
    99+
    2024-04-02
  • MongoDB使用场景总结
    很多人比较关心 MongoDB 的适用场景,也有用户在话题里分享了自己的业务场景,比如 案例1 用在应用服务器的日志记录,查找起来比文本灵活,导出也很方便。也是给应用练手,从外围系统...
    99+
    2024-04-02
  • Java ThreadLocal的使用场景总结
    目录使用场景1:本地变量① 2个线程格式化② 10个线程格式化③ 1000个线程格式化a) 线程安全问题分析b) 解决线程安全问题:加锁c) 解决线程安全问题:ThreadLocal...
    99+
    2024-04-02
  • mybatis动态sql常用场景总结
    目录前言1.if2. choose、when、otherwise3. trim、where、set4. foreach前言 平时在开发中,针对动态sql这块目前是薄弱点,自...
    99+
    2024-04-02
  • React Hooks如何使用
    这篇文章主要介绍了React Hooks如何使用的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇React Hooks如何使用文章都会有所收获,下面我们一起来看看吧。hooks介绍在react...
    99+
    2023-07-05
  • React中setState同步异步场景的使用
    目录setState同步异步场景描述原理保证内部数据统一启用并发更新参考setState同步异步场景 React通过this.state来访问state,通过this.setStat...
    99+
    2024-04-02
  • React中memouseCallbackuseMemo方法作用及使用场景
    目录memo()、useCallback()、useMemo()使用场景React.memo()React.useCallback()React.useMemo()memo()、us...
    99+
    2023-03-06
    React useCallback useMemo React useMemo
  • 16个Redis的常见使用场景
     Redis 16 个常见使用场景 1、缓存 String类型 例如:热点数据缓存(例如报表、明星出轨),对象缓存、全页缓存、可以提升热点数据的访问数据。 2、数据共享分布...
    99+
    2024-04-02
  • React使用ref方法与场景介绍
    目录摘要1.ref的挂载2.使用ref的三种方式3.ref的使用场景摘要 不管在Vue中还是React,如果我们想使用一个元素的DOM,不需要通过JS中操纵DOM的方法,它们提供了一...
    99+
    2022-11-13
    React ref React ref用法
  • hooks怎么在react中使用
    hooks怎么在react中使用?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。State Hooks案例:import { useState }&...
    99+
    2023-06-14
  • React-hooks中的useEffect使用步骤
    目录1.理解函数副作用什么是副作用常见的副作用2.基础使用使用步骤示例代码3.依赖项控制useEffect 永远是在 DOM渲染完成之后执行 1.理解函数副作用 什么是副作用 对于R...
    99+
    2024-04-02
  • React中的Context应用场景分析
    Context定义和目的 Context 提供了一种在组件之间共享数据的方式,而不必显式地通过组件树的逐层传递 props。 应用场景 哪些数据会需要共享? Context 设计目...
    99+
    2024-04-02
  • KotlinFlow常见场景下的使用实例
    目录Kotlin Flow在开发中的常用场景使用一、网络请求搭载Retrofit1.1 LiveDataCallAdapterFactory1.2 suspend二、协程与Flow的...
    99+
    2024-04-02
  • 怎么在React中使用Hooks
    这篇文章给大家介绍怎么在React中使用Hooks,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。一、State Hook1、基础用法function State(){  const&nbs...
    99+
    2023-06-14
  • jvm调优的几种场景(小结)
    目录一、cpu占用过高(1)用top命令查看cpu占用情况(2)用top -Hp命令查看线程的情况(3)把线程号转换为16进制(4)用jstack工具查看线程栈情况二、死锁三、内存泄...
    99+
    2024-04-02
  • React框架的应用场景有哪些
    React框架的应用场景包括但不限于以下几个方面: 单页应用(Single Page Application,SPA):Reac...
    99+
    2023-10-27
    React
  • React中memo、useCallback和useMemo的使用场景是什么
    这篇“React中memo、useCallback和useMemo的使用场景是什么”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这...
    99+
    2023-07-05
  • Cookie的使用场景
    Cookie是存在浏览器端的浏览器 第一次请求:浏览器端是没有cookie数据的 tomcat容器: (1)服务器端创建Cookie public Cookie(String name,String value) (2)设置cookie的过...
    99+
    2023-09-21
    服务器 运维
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作