iis服务器助手广告广告
返回顶部
首页 > 资讯 > 精选 >怎么在React中使用Hooks
  • 605
分享到

怎么在React中使用Hooks

2023-06-14 12:06:15 605人浏览 八月长安
摘要

这篇文章给大家介绍怎么在React中使用Hooks,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。一、State Hook1、基础用法function State(){  const&nbs

这篇文章给大家介绍怎么在React中使用Hooks,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。

一、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就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

--结束END--

本文标题: 怎么在React中使用Hooks

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

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

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

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

下载Word文档
猜你喜欢
  • C++ 生态系统中流行库和框架的贡献指南
    作为 c++++ 开发人员,通过遵循以下步骤即可为流行库和框架做出贡献:选择一个项目并熟悉其代码库。在 issue 跟踪器中寻找适合初学者的问题。创建一个新分支,实现修复并添加测试。提交...
    99+
    2024-05-15
    框架 c++ 流行库 git
  • C++ 生态系统中流行库和框架的社区支持情况
    c++++生态系统中流行库和框架的社区支持情况:boost:活跃的社区提供广泛的文档、教程和讨论区,确保持续的维护和更新。qt:庞大的社区提供丰富的文档、示例和论坛,积极参与开发和维护。...
    99+
    2024-05-15
    生态系统 社区支持 c++ overflow 标准库
  • c++中if elseif使用规则
    c++ 中 if-else if 语句的使用规则为:语法:if (条件1) { // 执行代码块 1} else if (条件 2) { // 执行代码块 2}// ...else ...
    99+
    2024-05-15
    c++
  • c++中的继承怎么写
    继承是一种允许类从现有类派生并访问其成员的强大机制。在 c++ 中,继承类型包括:单继承:一个子类从一个基类继承。多继承:一个子类从多个基类继承。层次继承:多个子类从同一个基类继承。多层...
    99+
    2024-05-15
    c++
  • c++中如何使用类和对象掌握目标
    在 c++ 中创建类和对象:使用 class 关键字定义类,包含数据成员和方法。使用对象名称和类名称创建对象。访问权限包括:公有、受保护和私有。数据成员是类的变量,每个对象拥有自己的副本...
    99+
    2024-05-15
    c++
  • c++中优先级是什么意思
    c++ 中的优先级规则:优先级高的操作符先执行,相同优先级的从左到右执行,括号可改变执行顺序。操作符优先级表包含从最高到最低的优先级列表,其中赋值运算符具有最低优先级。通过了解优先级,可...
    99+
    2024-05-15
    c++
  • c++中a+是什么意思
    c++ 中的 a+ 运算符表示自增运算符,用于将变量递增 1 并将结果存储在同一变量中。语法为 a++,用法包括循环和计数器。它可与后置递增运算符 ++a 交换使用,后者在表达式求值后递...
    99+
    2024-05-15
    c++
  • c++中a.b什么意思
    c++kquote>“a.b”表示对象“a”的成员“b”,用于访问对象成员,可用“对象名.成员名”的语法。它还可以用于访问嵌套成员,如“对象名.嵌套成员名.成员名”的语法。 c++...
    99+
    2024-05-15
    c++
  • C++ 并发编程库的优缺点
    c++++ 提供了多种并发编程库,满足不同场景下的需求。线程库 (std::thread) 易于使用但开销大;异步库 (std::async) 可异步执行任务,但 api 复杂;协程库 ...
    99+
    2024-05-15
    c++ 并发编程
  • 如何在 Golang 中备份数据库?
    在 golang 中备份数据库对于保护数据至关重要。可以使用标准库中的 database/sql 包,或第三方包如 github.com/go-sql-driver/mysql。具体步骤...
    99+
    2024-05-15
    golang 数据库备份 mysql git 标准库
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作