React—13—Hooks


一、hook的功能

简单总结一下hooks:
 它可以让我们在不编写class的情况下使用state以及其他的React特性;
 但是我们可以由此延伸出非常多的用法,来让我们前面所提到的问题得到解决;
◼ Hook的使用场景:
 Hook的出现基本可以代替我们之前所有使用class组件的地方;
 但是如果是一个旧的项目,你并不需要直接将所有的代码重构为Hooks,因为它完全向下兼容,你可以渐进式的来使用它;
 Hook只能在函数组件中使用,不能在类组件,或者函数组件之外的地方使用;
在我们继续之前,请记住 Hook 是:
 完全可选的:你无需重写任何已有代码就可以在一些组件中尝试 Hook。但是如果你不想,你不必现在就去学习或使用 Hook。
 100% 向后兼容的:Hook 不包含任何破坏性改动。
 现在可用:Hook 已发布于 v16.8.0。

 

 

 

 

 

 

 

 

二、useState

react提供的hooks函数只能在函数式组件和自定义hook里使用。

函数式组件:命名要大驼峰,否则不算函数式组件。

自定义hook:命名要以use开头。

 

二、useEffect

1.基本使用

◼ 目前我们已经通过hook在函数式组件中定义state,那么类似于生命周期这些呢?
 Effect Hook 可以让你来完成一些类似于class中生命周期的功能;
 事实上,类似于网络请求、手动更新DOM、一些事件的监听,都是React更新DOM的一些副作用(Side Effects);
 所以对于完成这些功能的Hook被称之为 Effect Hook;
◼ useEffect的解析:
 通过useEffect的Hook,可以告诉React需要在渲染后执行某些操作;
 useEffect要求我们传入一个回调函数,在React执行完更新DOM操作之后,就会回调这个函数;
 默认情况下,无论是第一次渲染之后,还是每次更新之后,都会执行这个 回调函数;(useEffect()里的回调函数,会在函数组件执行完之后自动执行。)
 
所以,useEffect的执行时机,类似于componentDisMount() 、componenttUpdated()钩子函数加一起的功能。
 
 

2.清除执行

◼ 在class组件的编写过程中,某些副作用的代码,我们需要在componentWillUnmount中进行清除:
比如我们之前的事件总线或Redux中手动调用subscribe; 都需要在componentWillUnmount有对应的取消订阅;
 
 Effect Hook通过什么方式来模拟componentWillUnmount呢?
◼ useEffect传入的回调函数A本身可以有一个返回值,这个返回值是另外一个回调函数B,React 会在组件更新和卸载的时候执行清除操作即执行这个回调函数B;
  useEffect(() => {
    console.log(' [ 开始监听redux的store]');
    return () => {
      console.log(' [ 取消监听redux的store]');
    };
  });

 

◼ 为什么要在 effect 中返回一个函数?
 这是 effect 可选的清除机制。每个 effect 都可以返回一个清除函数;
 如此可以将添加和移除订阅的逻辑放在一起;它们都属于 effect 的一部分,这样就体现了高内聚,省的代码写的分散;
 

useEffect的回调函数会在dom首次挂载好或者重新渲染更新好之后,执行。

useEffect的回调函数的返回函数,会在dom即将卸载时或者即将重新渲染时,执行。

 

3.使用多个Effect

◼ 使用Hook的其中一个目的就是解决class中生命周期经常将很多的逻辑放在一起的问题:
 比如网络请求、事件监听、手动修改DOM,这些往往都会放在componentDidMount中,但是在cimponentDisMount中所有的代码都要写到这里,显得很臃肿;
 
◼ 使用Effect Hook,我们可以将它们分离到不同的useEffect中:
 React 将按照 effect 声明的顺序依次调用组件中的每一个 effect;
 
 

4.Effect执行时机

我们刚刚说,useEffect的回调函数会在dom首次挂载好或者重新渲染更新好之后,执行。类似于componentDisMount() 、componenttUpdated()钩子函数加一起的功能

这句话其实是在useEffect未指定依赖的时候的执行时机,如果useEffect指定了依赖,那么useEffect就只会在首次挂载和依赖发生变化后才更新。

 

◼ 默认情况下,useEffect的回调函数会在每次渲染时都重新执行,但是这会导致两个问题:
 某些代码我们只是希望执行一次即可,类似于componentDidMount和componentWillUnmount中完成的事情;(比如网络请求、订阅和取消订阅);不需要在componenttUpdated()yy也执行
 另外,多次执行也会导致一定的性能问题;
 
◼ 我们如何决定useEffect在什么时候应该执行和什么时候不应该执行呢? useEffect实际上有两个参数:
 参数一:决定执行的回调函数a,回调函数返回的函数b;
 参数二:决定执行时机;该useEffect在哪些state发生变化时,才重新执行;(受谁的影响) 但是,如果一个函数我们不希望依赖任何的内容时,也可以传入一个空的数组 [],这样useEffect就只有在首次挂载的时候才会执行。

 

 
 
 

5.总结

总的感觉这个函数很强大,即有钩子函数的功能,通过第二个参数,又能实现vue侦听器的功能。

 

参数二:
1.不填,则首次挂载和后续重新渲染好之后,都会调用useEffect的回调函数a,类似类似于componentDisMount() 、componenttUpdated()。当然,回调函数返回的函数b则会在dom卸载前或每次重新渲染之前调用。
2.填[ ],,则谁都不依赖,只在首次挂载好之后执行a。在卸载dom前执行b。
3.填 [ foo], 则首次挂载和以为foo变化导致的重新渲染render渲染好之后,都会再执行一次useEffect。 在卸载dom前和foo导致的重新渲染之前执行b。
 
 
 

三、useContext

之前想使用context的值太麻烦了,hook很简单。这不过useContext的返回值只一个变量,不再是数组了,不用解构了。

 

 

import React, { memo } from 'react';
import './style.css';
import { useContext } from 'react';

import { UserContext, ThemeContext } from './context';
const App = memo(() => {
  const user = useContext(UserContext);
  const theme = useContext(ThemeContext);

  return (
    <div>
      <h1> App: {user.name + user.age} </h1>
      <div style={{ color: theme.color, fontSize: theme.fontSize }}>hhh</div>
    </div>
  );
});

export default App;

 

 

 

四、react-redux里得hook

1.基本使用

useSelector和useDispatch

2.useSelectot触发重新渲染

问题:

home组件使用memo包裹,那么props只要没有变化,那么home组件不应该重新渲染,为什么我们点击app组件的按钮使得count加1时,home组件也会重新渲染?

原因:

useSelector 钩子会订阅整个 Redux store 的变化。每当 store 更新时,useSelector 会重新执行您提供的选择器函数,获取新的选择结果。

然后,useSelector 会将新旧选择结果进行比较,以确定组件是否需要重新渲染。默认情况下,这种比较是通过严格的引用相等性(===)来进行的。

我们的选择器是一个箭头函数,并且返回一个对象{foo:xxx}给组件做解构,所以选择器函数返回的这是个对象,当选择器重新执行时,有返回一个新的对象{foo:xxx},由于引用地址不一样,那么在useSelector眼里自然是false,所以redux就要重新渲染我们的函数式组件。

解决:

对于对象来说,不管是相等运算符(==)还是严格相等运算符(===),只有当两个对象引用同一个内存地址时,比较结果才为 true

但我们可以使用react-redux提供的shollwEqual来帮我们实现浅层比较,这个方法只会比较对象的每个属性值,只要属性值相等,就认为是同一个对象,这样就不会重新渲染了,这样就不会再出现其他组件修改了store和本组件引用无关得变量,导致本组件会重新渲染得事,可以做性能优化。

import { useSelector, useDispatch, shallowEqual } from 'react-redux';
import { addNumberAction } from './store/features/counter';
const Home
= memo(() => { console.log('home render'); const data = useSelector(state => ({ foo: state.counter.foo }),shallowEqual); return ( <div style={{ border: '1px solid red' }}> <h1> home: {data.foo} </h1> </div> ); }); const App = memo(() => { console.log('app render'); const data = useSelector(state => ({ count: state.counter.count, })); const dispatch = useDispatch(); return ( <div> <h1> app: <hr /> {data.count} <button onClick={e => dispatch(addNumberAction(1))}>+1</button> </h1>
<Home></Home> </div> ); }); export default App;

 同理,我们可以给app组件的useSelector也加上,shallowEqual,这样home组件修改foo时,app组件也不会重新渲染了。

 react中,父组件渲染,子组件不一样跟着必须重新渲染,反之亦然,因为可以通过shouldComponentUpdata、pureComponent、memo优化只有props改变时才渲染。

 vue中,父组件渲染,子组件是不是一定更着重新渲染?也不是的,只有父组件给子组件传递的props改变时子组件才会重新渲染,这是vue内部帮我们做好的。

 

四、其他hook

 useReducer

感觉没啥用

  

useCallBack

useCallBack,我的理解是,当我们给子组件传递一个函数a作为props时,我们父组件的其他变量变化时,由于通过setXXX进行的,所以父组件会重新渲染,那么函数a也会重新定义,那么函数啊的引用地址就变化了,那么子组件即使被memo()包裹了,也会重新渲染,如果子组件内容非常多,这种渲染就是没有必要的,毕竟父组件变化的是跟子组件无关的变量。

这个时候可用usecallfack,usecallfack接收两个参数,第一个参数定义一个函数a,第二个参数里指定他的依赖比如count,

只有他的依赖变化时,usecallfack才会使用新定义的函数a,否则一致使用的是老a

如果是老a,那么其他变量变化引用的父组件重新渲染,而传递给子组件的函数一直是老的a的引用地址,那么子组件就不会变化。

 

 useMemo

useMemo和useCallback的区别是,useMemo返回的是值,类似于vue的计算属性,useCallback返回的是函数;

 

 

useRef()的两个功能:

1.获取dom

类组件使用createRef(),函数式组件使用useRef();

2.由于useRef()返回的对象,是在函数的整个生命周期都不发生改变的,所以可以用这个对象来保存一些数据,比如避免闭包陷阱。

 

 

useImperativeHandle

 

 

useLayoutEffect

它类似于vue的beforeMount和beforeUpdate,在挂载前执行的操作。

如果在useLayoutEffect又触发了重新渲染render(),那么原有的渲染将会停止,将会渲染useLayoutEffect触发的渲染。

 

 

 

 useId

指的是服务端渲染,服务端已经把页面都创建好了即都渲染好了,是一个完整的html,就跟以前的jsp一样,浏览器拿到之后,直接解析即可。

客户端渲染: 也就是我们的spa单页面富应用,虽然浏览器也会从服务器拿到一个html,但是这个html里的dom都是不完整的,只有一个id=root的div,还有一些其他的meta、title等。

浏览器需要加载东西的,比如说我们打包压缩的的js文件时<script src='buddle,js'></script>,然后浏览器去加载这个js,然后又要开始document.createElelment(ul) 、document.createElelment(li) 等等创建元素,去生成完整的页面结构,最终才渲染出一个完整的html。

 hydration是ssr同构的一个过程,就是这个hydration保证了同一套代码在浏览器渲染时的交互。

具体的ssr后续再说,这里只是大概说下概念。

 

◼ 我们再来看一遍:useId 是一个用于生成横跨服务端和客户端的稳定的唯一 ID 的同时避免 hydration 不匹配的 hook。
这个东西可以保证,只要是这个函数式组件内的用useId生成的id,永远都不会变,类似useRef,但是useId甚至可以跨端都不变;
◼ 所以我们可以得出如下结论:
 useId是用于react的同构应用开发的,前端的SPA页面并不需要使用它;
 useId可以保证应用程序在客户端和服务器端生成唯一的ID,这样可以有效的避免通过一些手段生成的id不一致,造成
hydration mismatch

 

 

useTransition

 

 

 

useDeferredValue

 

posted @ 2025-03-08 19:26  Eric-Shen  阅读(18)  评论(0)    收藏  举报