网站建设费用一年,企业网站排名怎么做,租用网站,视频网站备案怎么做本文来自#React系列教程#xff1a;https://mp.weixin.qq.com/mp/appmsgalbum?__bizMzg5MDAzNzkwNAactiongetalbumalbum_id1566025152667107329) 一. Hook高级使用
1.1. useReducer
很多人看到useReducer的第一反应应该是redux的某个替代品#xff0c;其实并不是… 本文来自#React系列教程https://mp.weixin.qq.com/mp/appmsgalbum?__bizMzg5MDAzNzkwNAactiongetalbumalbum_id1566025152667107329) 一. Hook高级使用
1.1. useReducer
很多人看到useReducer的第一反应应该是redux的某个替代品其实并不是。
useReducer仅仅是useState的一种替代方案
在某些场景下如果state的处理逻辑比较复杂我们可以通过useReducer来对其进行拆分或者这次修改的state需要依赖之前的state时也可以使用
单独创建一个reducer/counter.js文件
export function counterReducer(state, action) {switch(action.type) {case increment:return {...state, counter: state.counter 1}case decrement:return {...state, counter: state.counter - 1}default:return state;}
}home.js:
import React, { useReducer } from react
import { counterReducer } from ../reducer/counterexport default function Home() {const [state, dispatch] useReducer(counterReducer, {counter: 100});return (divh2当前计数: {state.counter}/h2button onClick{e dispatch({type: increment})}1/buttonbutton onClick{e dispatch({type: decrement})}-1/button/div)
}我们来看一下如果我们创建另外一个profile.js也使用这个reducer函数是否会进行数据的共享
import React, { useReducer } from react
import { counterReducer } from ../reducer/counterexport default function Profile() {const [state, dispatch] useReducer(counterReducer, {counter: 0});return (divh2当前计数: {state.counter}/h2button onClick{e dispatch({type: increment})}1/buttonbutton onClick{e dispatch({type: decrement})}-1/button/div)
}数据是不会共享的它们只是使用了相同的counterReducer的函数而已。
所以useReducer只是useState的一种替代品并不能替代Redux。
1.2. useCallback
useCallback实际的目的是为了进行性能的优化。
如何进行性能的优化呢
useCallback会返回一个函数的 memoized记忆的 值在依赖不变的情况下多次定义的时候返回的值是相同的
const memoizedCallback useCallback(() {doSomething(a, b);},[a, b]
);我们来看下面一段很有趣的代码
import React, { memo, useState, useCallback } from reactexport default function CallbackHookDemo() {const [count, setCount] useState(0);const increment1 useCallback(function increment() {setCount(count 1);}, []);const increment2 function() {setCount(count 1);}return (divh2当前计数: {count}/h2button onClick{increment1}1/buttonbutton onClick{increment2}1/button/div)
}increment1在每次函数组件重新渲染时会返回相同的值increment2每次定义的都是不同的值
问题是否increment1会比increment2更加节省性能呢
事实上经过一些测试并没有更加节省内存因为useCallback中还是会传入一个函数作为参数所以并不存在increment2每次创建新的函数而increment1不需要创建新的函数这种性能优化
那么为什么说useCallback是为了进行性能优化呢
我们来对上面的代码进行改进
import React, { memo, useState, useCallback } from react;const CounterIncrement memo((props) {console.log(CounterIncrment被渲染:, props.name);return button onClick{props.increment}1/button
})export default function CallbackHookDemo() {const [count, setCount] useState(0);const increment1 useCallback(function increment() {setCount(count 1);}, []);const increment2 function() {setCount(count 1);}return (divh2当前计数: {count}/h2{/* button onClick{increment1}1/buttonbutton onClick{increment2}1/button */}CounterIncrement increment{increment1} nameincrement1/CounterIncrement increment{increment2} nameincrement2//div)
}在上面的代码中我们将回调函数传递给了子组件在子组件中会进行调用在发生点击时我们会发现接受increment1的子组件不会重新渲染但是接受increment2的子组件会重新渲染所以useCallback最主要用于性能渲染的地方应该是和memo结合起来决定子组件是否需要重新渲染
1.3. useMemo
useMemo实际的目的也是为了进行性能的优化。
如何进行性能的优化呢
useMemo返回的也是一个 memoized记忆的 值在依赖不变的情况下多次定义的时候返回的值是相同的
const memoizedValue useMemo(() computeExpensiveValue(a, b), [a, b]);我们来看一个案例
import React, { useState, useMemo } from react;function calcNum(count) {let total 0;for (let i 0; i count; i) {total i;}console.log(计算一遍);return total
}export default function MemoHookDemo() {const [count, setCount] useState(10);const [isLogin, setIsLogin] useState(true);const total calcNum(count);return (divh2数字和: {total}/h2button onClick{e setCount(count 1)}1/button{isLogin h2Coderwhy/h2}button onClick{e setIsLogin(!isLogin)}切换/button/div)
}无论我们点击了是 1还是 切换 案例都会重新计算一次事实上我们只是希望在count发生变化时重新计算
这个时候我们可以使用useMemo来进行性能的优化
import React, { useState, useMemo } from react;function calcNum(count) {let total 0;for (let i 0; i count; i) {total i;}console.log(计算一遍);return total
}export default function MemoHookDemo() {const [count, setCount] useState(10);const [isLogin, setIsLogin] useState(true);const total useMemo(() {return calcNum(count);}, [count]);return (divh2数字和: {total}/h2button onClick{e setCount(count 1)}1/button{isLogin h2Coderwhy/h2}button onClick{e setIsLogin(!isLogin)}切换/button/div)
}当然useMemo也可以用于子组件的性能优化
import React, { useState, useMemo, memo } from react;function calcNum(count) {let total 0;for (let i 0; i count; i) {total i;}console.log(计算一遍);return total
}const ShowCounter memo((props) {console.log(重新渲染);return h1Counter: {props.total}/h1
})const ShowInfo memo((props) {console.log(ShowInfo重新渲染);return h1信息: {props.info.name}/h1
})export default function MemoHookDemo() {const [count, setCount] useState(10);const [isLogin, setIsLogin] useState(true);const total useMemo(() {return calcNum(count);}, [count]);const info useMemo(() {return {name: why}}, [])return (divh2数字和: {total}/h2ShowCounter total{total} /ShowInfo info{info}/button onClick{e setCount(count 1)}1/button{isLogin h2Coderwhy/h2}button onClick{e setIsLogin(!isLogin)}切换/button/div)
}ShowCounter子组件依赖的是一个基本数据类型所以在比较的时候只要值不变那么就不会重新渲染ShowInfo接收的是一个对象每次都会定义一个新的对象所以我们需要通过useMemo来对其进行优化
1.4. useRef
useRef返回一个ref对象返回的ref对象在组件的整个生命周期保持不变。
最常用的ref是两种用法
用法一引入DOM或者组件但是需要是class组件元素用法二保存一个数据这个对象在整个生命周期中可以保存不变
用法一引用DOM
import React, { useRef } from react;export default function RefHookDemo() {const inputRef useRef();const titleRef useRef();const handleOperating () {titleRef.current.innerHTML 我是coderwhy;inputRef.current.focus();}return (divinput typetext ref{inputRef}/h2 ref{titleRef}默认内容/h2button onClick{e handleOperating()}操作/button/div)
}用法二使用ref保存上一次的某一个值
useRef可以想象成在ref对象中保存了一个.current的可变盒子useRef在组件重新渲染时返回的依然是之前的ref对象但是current是可以修改的
import React, { useState, useEffect, useRef } from react;let preValue 0;export default function RefHookDemo02() {const [count, setCount] useState(0);const countRef useRef(count);useEffect(() {countRef.current count;}, [count]);return (divh2前一次的值: {countRef.current}/h2h2这一次的值: {count}/h2button onClick{e setCount(count 1)}1/button/div)
}1.5. useImperativeHandle
useImperativeHandle并不是特别好理解我们一点点来学习。
我们先来回顾一下ref和forwardRef结合使用
通过forwardRef可以将ref转发到子组件子组件拿到父组件中创建的ref绑定到自己的某一个元素中
import React, { useRef, forwardRef } from react;const HYInput forwardRef(function (props, ref) {return input typetext ref{ref}/
})export default function ForwardDemo() {const inputRef useRef();return (divHYInput ref{inputRef}/button onClick{e inputRef.current.focus()}聚焦/button/div)
}上面的做法本身没有什么问题但是我们是将子组件的DOM直接暴露给了父组件
直接暴露给父组件带来的问题是某些情况的不可控父组件可以拿到DOM后进行任意的操作
但是事实上在上面的案例中我们只是希望父组件可以操作的focus其他并不希望它随意操作
通过useImperativeHandle可以只暴露固定的操作
通过useImperativeHandle的Hook将传入的ref和useImperativeHandle第二个参数返回的对象绑定到了一起所以在父组件中使用 inputRef.current时实际上使用的是返回的对象比如我调用了 focus函数甚至可以调用 printHello函数
import React, { useRef, forwardRef, useImperativeHandle } from react;const HYInput forwardRef(function (props, ref) {// 创建组件内部的refconst inputRef useRef();useImperativeHandle(ref, () ({focus: () {inputRef.current.focus();},printHello: () {console.log(Hello World)}}))// 这里绑定的是组件内部的inputRefreturn input typetext ref{inputRef}/
})export default function ImperativeHandleHookForwardDemo() {const inputRef useRef();return (divHYInput ref{inputRef}/button onClick{e inputRef.current.focus()}聚焦/buttonbutton onClick{e inputRef.current.printHello()}Hello World/button/div)
}1.6. useLayoutEffect
useLayoutEffect看起来和useEffect非常的相似事实上他们也只有一点区别而已
useEffect会在渲染的内容更新到DOM上之后执行不会阻塞DOM的更新useLayoutEffect会在渲染的内容更新到DOM上之前执行会阻塞DOM的更新
如果我们希望在某些操作发生之后再更新DOM那么应该将这个操作放到useLayoutEffect。
我们来看下面的一段代码
import React, { useEffect, useState, useLayoutEffect } from react;export default function EffectHookDemo() {const [count, setCount] useState(0);useEffect(() {if (count 0) {setCount(Math.random()*200)}}, [count]);return (divh2当前数字: {count}/h2button onClick{e setCount(0)}随机数/button/div)
}这段代码在开发中会发生闪烁的现象因为我们先将count设置为了0那么DOM会被更新并且会执行一次useEffect中的回调函数在useEffect中我们发现count为0又执行一次setCount操作那么DOM会再次被更新并且useEffect又会被执行一次
事实上我们上面的操作的目的是在count被设置为0时随机另外一个数字
如果我们使用useLayoutEffect那么会等到useLayoutEffect代码执行完毕后再进行DOM的更新
import React, { useEffect, useState, useLayoutEffect } from react;export default function EffectHookDemo() {const [count, setCount] useState(0);useLayoutEffect(() {if (count 0) {setCount(Math.random()*200)}}, [count]);return (divh2当前数字: {count}/h2button onClick{e setCount(0)}随机数/button/div)
}二. 自定义Hook
2.1. 认识自定义hook
自定义Hook本质上只是一种函数代码逻辑的抽取严格意义上来说它本身并不算React的特性。
需求所有的组件在创建和销毁时都进行打印
组件被创建打印 组件被创建了组件被销毁打印 组件被销毁了
export default function CustomHookDemo() {useEffect(() {console.log(组件被创建了);return () {console.log(组件被销毁了);}}, [])return (divh2CustomHookDemo/h2/div)
}但是这样来做意味着所有的组件都需要有对应的逻辑
function Home(props) {useEffect(() {console.log(组件被创建了);return () {console.log(组件被销毁了);}}, [])return h2Home/h2
}function Profile(props) {useEffect(() {console.log(组件被创建了);return () {console.log(组件被销毁了);}}, [])return h2Profile/h2
}如何可以对它们的逻辑进行抽取呢
我们可能希望抽取到一个函数中
function loggingLife() {useEffect(() {console.log(组件被创建了);return () {console.log(组件被销毁了);}}, [])
}但是抽取到这里调用之后代码是报错的 原因是普通的函数中不能使用hook
那么我们应该如何操作呢
非常简单函数以特殊的方式命名以 use 开头即可
function useLoggingLife() {useEffect(() {console.log(组件被创建了);return () {console.log(组件被销毁了);}}, [])
}当然自定义Hook可以有参数也可以有返回值
function useLoggingLife(name) {useEffect(() {console.log(${name}组件被创建了);return () {console.log(${name}组件被销毁了);}}, [])
}2.2. 自定义Hook练习
我们通过一些案例来练习一下自定义Hook。
使用User、Token的Context
比如多个组件都需要使用User和Token的Context
import React, { useContext } from react
import { UserContext, TokenContext } from ../Appexport default function CustomHookContextDemo() {const user useContext(UserContext);const token useContext(TokenContext);console.log(user, token);return (divh2CustomHookContextDemo/h2/div)
}这段代码我们在每次使用user和token时都需要导入对应的Context并且需要使用两次useContext
我们可以抽取到一个自定义Hook中
function useUserToken() {const user useContext(UserContext);const token useContext(TokenContext);return [user, token];
}获取窗口滚动的位置
在开发中某些场景我们可能总是希望获取创建滚动的位置
import React, { useEffect, useState } from reactexport default function CustomScrollPositionHook() {const [scrollPosition, setScrollPosition] useState(0);useEffect(() {const handleScroll () {setScrollPosition(window.scrollY);}document.addEventListener(scroll, handleScroll);return () {document.removeEventListener(scroll, handleScroll);}}, [])return (div style{{padding: 1000px 0}}h2 style{{position: fixed, top: 0, left: 0}}CustomScrollPositionHook: {scrollPosition}/h2/div)
}但是如果每一个组件都有对应这样的一个逻辑那么就会存在很多的冗余代码
function useScrollPosition() {const [scrollPosition, setScrollPosition] useState(0);useEffect(() {const handleScroll () {setScrollPosition(window.scrollY);}document.addEventListener(scroll, handleScroll);return () {document.removeEventListener(scroll, handleScroll);}}, [])return scrollPosition;
}数据存储的localStorage
在开发中我们会有一些数据希望通过localStorage进行存储当然你可以根据自己的情况选择sessionStorage
import React, { useState, useEffect } from reactexport default function CustomDataStoreHook() {const [name, setName] useState(() {return JSON.parse(window.localStorage.getItem(name))});useEffect(() {window.localStorage.setItem(name, JSON.stringify(name));}, [name])return (divh2CustomDataStoreHook: {name}/h2button onClick{e setName(coderwhy)}设置name/button/div)
}如果每一个里面都有这样的逻辑那么代码就会变得非常冗余
function useLocalStorange(key) {const [data, setData] useState(() {return JSON.parse(window.localStorage.getItem(key))});useEffect(() {window.localStorage.setItem(key, JSON.stringify(data));}, [data]);return [data, setData];
}三. Hook原理分析
这里我主要分析一下useState的原理因为本次教程是穿插讲解源码的所以不会所有源码一一讲解。
3.1. useState代码位置
useState还是从React中导入的所以我们可以先查看 点到useState的源码中
useState本质上是使用的dispatcher的useState dispatcher来自另外的一个函数resolveDispatcher 运行的过程中会赋值一个current的值是一个Dispatcher类型
Dispatcher来自于 react-reconciler/src/ReactFiberHooks Dispatch类型的定义 这里的Dispatch仅仅是一个类型而已我们赋值具体的值在不同的阶段是不同的
在挂载阶段HooksDispatcherOnMount在更新阶段HooksDispatcherOnUpdate 挂载哪一个取决于renderWithHook函数 3.2. HooksDispatcherOnMount
HooksDispatcherOnMount对应的useState是mountState mountState的源码 绑定的dispatchAction函数事实上是将所有的action放到了queue的队列中 3.3. HooksDispatcherOnUpdate
HooksDispatcherOnUpdate对应的useState是updateState updateState本质上会执行updateReducer
所有其实就更新阶段而言useState本质上用的是updateReducer updateReducer的源码如下 四. Redux Hooks
在之前的redux开发中为了让组件和redux结合起来我们使用了react-redux中的connect
但是这种方式必须使用高阶函数结合返回的高阶组件并且必须编写mapStateToProps和 mapDispatchToProps映射的函数
在Redux7.1开始提供了Hook的方式我们再也不需要编写connect以及对应的映射函数了
4.1. useSelector使用
useSelector的作用是将state映射到组件中
参数一将state映射到需要的数据中参数二可以进行比较来决定是否组件重新渲染后续讲解
const result: any useSelector(selector: Function, equalityFn?: Function)现在我可以改进一下之前的Profile中使用redux的代码
function Profile(props) {const {banners, recommends, counter} useSelector(state ({banners: state.homeInfo.banners,recommends: state.homeInfo.recommends}));console.log(Profile重新渲染);return (divh2数字: {counter}/h2h1Banners/h1ul{banners.map((item, index) {return li key{item.acm}{item.title}/li})}/ulh1Recommends/h1ul{recommends.map((item, index) {return li key{item.acm}{item.title}/li})}/ul/div)
}但是这段代码会有一个问题
当前我们的组件并不依赖counter但是counter发生改变时依然会引起Profile的重新渲染
原因是什么呢
useSelector默认会比较我们返回的两个对象是否相等如何比较呢const refEquality (a, b) a b也就是我们必须返回两个完全相等的对象才可以不引起重新渲染
这个时候我们可以使用react-redux中给我们提供的 shallowEqual const {banners, recommends, counter} useSelector(state ({banners: state.homeInfo.banners,recommends: state.homeInfo.recommends}), shallowEqual);这段代码的作用是避免不必要的重新渲染
当然你也可以编写自己的比较函数来决定是否重新渲染。
4.2. useDispatch
useDispatch非常简单就是直接获取dispatch函数之后在组件中直接使用即可
const dispatch useDispatch()直接使用dispatch
button onClick{e dispatch(subAction(1))}-1/button
button onClick{e dispatch(subAction(5))}-5/button我们还可以通过useStore来获取当前的store对象
const store useStore()在组件中可以使用store
const store useStore();
console.log(store.getState());