React—13—Hooks
一、hook的功能
二、useState
react提供的hooks函数只能在函数式组件和自定义hook里使用。
函数式组件:命名要大驼峰,否则不算函数式组件。
自定义hook:命名要以use开头。
二、useEffect
1.基本使用
2.清除执行
useEffect(() => { console.log(' [ 开始监听redux的store]'); return () => { console.log(' [ 取消监听redux的store]'); }; });
useEffect的回调函数会在dom首次挂载好或者重新渲染更新好之后,执行。
useEffect的回调函数的返回函数,会在dom即将卸载时或者即将重新渲染时,执行。
3.使用多个Effect
4.Effect执行时机
我们刚刚说,useEffect的回调函数会在dom首次挂载好或者重新渲染更新好之后,执行。类似于componentDisMount() 、componenttUpdated()钩子函数加一起的功能
这句话其实是在useEffect未指定依赖的时候的执行时机,如果useEffect指定了依赖,那么useEffect就只会在首次挂载和依赖发生变化后才更新。
5.总结
总的感觉这个函数很强大,即有钩子函数的功能,通过第二个参数,又能实现vue侦听器的功能。
三、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后续再说,这里只是大概说下概念。
useTransition
useDeferredValue