目录:
1 认识和体验Hooks
2 State/Effect
3 Context/Reducer
4 Callback/Memo
5 Ref/LayoutEffect
6 自定义Hooks使用
之后就可以多用hooks开发了,但是类组件的使用方法还是需要知道
一、认识和体验Hooks
函数是组件的缺陷:
1、改变函数组件里面的变量不会触发render重新渲染
2、能够触发重新渲染的条件,由于没有地方保存新的变量值,最后渲染出来的变量值还是默认值
3、没有生命周期
类组件的缺陷:
(1)复杂逻辑时候代码臃肿
(2)class本身就复杂、this指向什么的
(3)需要使用高阶函数来实现一些东西的使用
使用hooks可以简化代码量、看起来简洁,也不再有this指向问题
hooks必须放在函数的顶层使用,放在其他地方就会报错
正确:
报错:
类组件中不能使用hooks,也不能在其他普通函数里面使用hooks。但是在自定义hooks里面可以使用hooks。
普通函数使用hooks会报错:
自定义hooks里面使用其他hooks是允许的:
二、State/Effect
1、const [ 第一个参数是返回值的,第二个参数是原来重新设置存储值的函数 ] = useState(初始值)的括号里面要有值,否则初始值为undefined
第一个参数类似this.state;,第二个参数类似setState
(1)函数被调用完成的时候会被销毁,里面的变量没了,useState相当于在react的另外一个函数里面保存了这个变量,之后在取出来复用,并且调用的函数修改变量值相当于是在调用保存变量的那个函数
2、副作用的意思是页面渲染完成之后的一些额外可以使用的功能,比如网络请求、生命周期、dom操作等等
(1)useEffect(副作用需要放到单独的地方):
以下是副作用没有放到单独的地方:
以下是副作用放到单独的地方:
(2)清除副作用里面的一些监听事件(清除机制)
注意这个useEffect里面的代码在每次数据更新的时候都会执行一次,(4)会讲怎么让useEffect的代码只执行一次。
取消监听的方法就是在写个return一个函数
(3)一个组件里面可以使用多次useEffect来分别放不同的代码
(4)useEffect性能优化,控制回调执行,让一些东西只执行一次或者受谁影响才发生改变。
需要控制某个代码因为什么发生改变才执行的话就是在useEffect的第二个参数写数组,数组里面写受谁影响:
只需要在useEffect的第二个参数写一个空数组 []就可以只在组件加载是执行一次,以后不管谁发生改变都不在执行这个useEffect包裹的函数:
三、Context/Reducer
后续使用的hooks偏向于特殊场景使用,上面的useState和useEffect是必须要回的基础的。
1、useContext的使用:
(1)创建context的文件夹和index.js文件:
(2)App.jsx对应的index.js文件里面提供数据:
(3)被包裹的App.jsx里面使用提供的参数:
2、useReducer(了解)
相当于useState的替代品
使用useReducer的场景就是下图,可以一次管理多个参数,但是修改参数的代码得放到reducer里面反而不好操作:
useReducer使用方法:
import React, { memo, useReducer } from 'react' // import { useState } from 'react' function reducer(state, action) { switch(action.type) { case "increment": return { ...state, counter: state.counter + 1 } case "decrement": return { ...state, counter: state.counter - 1 } case "add_number": return { ...state, counter: state.counter + action.num } case "sub_number": return { ...state, counter: state.counter - action.num } default: return state } } // useReducer+Context => redux const App = memo(() => { // const [count, setCount] = useState(0) const [state, dispatch] = useReducer(reducer, { counter: 0, friends: [], user: {} }) // const [counter, setCounter] = useState() // const [friends, setFriends] = useState() // const [user, setUser] = useState() return ( <div> {/* <h2>当前计数: {count}</h2> <button onClick={e => setCount(count+1)}>+1</button> <button onClick={e => setCount(count-1)}>-1</button> <button onClick={e => setCount(count+5)}>+5</button> <button onClick={e => setCount(count-5)}>-5</button> <button onClick={e => setCount(count+100)}>+100</button> */} <h2>当前计数: {state.counter}</h2> <button onClick={e => dispatch({type: "increment"})}>+1</button> <button onClick={e => dispatch({type: "decrement"})}>-1</button> <button onClick={e => dispatch({type: "add_number", num: 5})}>+5</button> <button onClick={e => dispatch({type: "sub_number", num: 5})}>-5</button> <button onClick={e => dispatch({type: "add_number", num: 100})}>+100</button> </div> ) }) export default App
四、Callback/Memo
注意:如果实在不懂,就只需要知道,以后在父组件要向子组件传递函数的时候,需要通过useCallback包裹就好了。
1、useCallback用于性能优化
(1)原因分析:在函数式组件里面定义的函数,在被调用函数并发生重新渲染的时候,函数都会被重新定义,然后原来的那个函数会被销毁掉。
之前的函数被销毁掉
(2)使用useCallback可以优化你每次使用的代码还是原来的callback包裹的函数,但是被包裹的那个函数每次函数式组件发生重新渲染的时候都还是会被重新定义,所以没效果。
(3)回调陷阱:
(4)useCallback一般用在父组件给子组件传递函数的时候做优化的,避免父组件让子组件一直重新渲染执行子组件的函数降低性能。原因是因为,子组件如果里面有很多的函数,其中有一个函数是从父组件传递过来的,那当父组件发生重新渲染的时候,会让函数重新创建,子组件的props从父组件获取的函数也就重新获取了,子组件发生重新渲染,子组件里面的很多函数也重新创建。性能下降。
import React, { memo, useState, useCallback, useRef } from 'react' // useCallback性能优化的点: // 1.当需要将一个函数传递给子组件时, 最好使用useCallback进行优化, 将优化之后的函数, 传递给子组件 // props中的属性发生改变时, 组件本身就会被重新渲染 const HYHome = memo(function(props) { const { increment } = props console.log("HYHome被渲染") return ( <div> <button onClick={increment}>increment+1</button> {/* 100个子组件 */} </div> ) }) const App = memo(function() { const [count, setCount] = useState(0) const [message, setMessage] = useState("hello") // 闭包陷阱: useCallback // const increment = useCallback(function foo() { // console.log("increment") // setCount(count+1) // }, [count]) // 进一步的优化: 当count发生改变时, 也使用同一个函数(了解) // 做法一: 将count依赖移除掉, 缺点: 闭包陷阱 // 做法二: useRef, 在组件多次渲染时, 返回的是同一个值 const countRef = useRef() countRef.current = count const increment = useCallback(function foo() { console.log("increment") setCount(countRef.current + 1) }, []) // 普通的函数 // const increment = () => { // setCount(count+1) // } return ( <div> <h2>计数: {count}</h2> <button onClick={increment}>+1</button> <HYHome increment={increment}/> <h2>message:{message}</h2> <button onClick={e => setMessage(Math.random())}>修改message</button> </div> ) }) // function foo(name) { // function bar() { // console.log(name) // } // return bar // } // const bar1 = foo("why") // bar1() // why // bar1() // why // const bar2 = foo("kobe") // bar2() // kobe // bar1() // why export default App
注意:useCallback和usememo的区别是前者返回的是函数,后者返回的是值。
简单说,useMemo的作用就是避免函数式组件因为一个参数变化导致重新渲染而其他函数重复执行多个函数 和避免传递给子组件的对象类型参数由于组件重新渲染而重新创建对象,子组件发现对象变化,子组件发生无意义重新渲染。
2、usememo用于优化单独函数组件里面,某些函数发生变量变化导致重新渲染整个组件,组件内部其他函数又全部重新执行一次,浪费性能。
import React, { memo, useCallback } from 'react' import { useMemo, useState } from 'react' const HelloWorld = memo(function(props) { console.log("HelloWorld被渲染~") return <h2>Hello World</h2> }) function calcNumTotal(num) { // console.log("calcNumTotal的计算过程被调用~") let total = 0 for (let i = 1; i <= num; i++) { total += i } return total } const App = memo(() => { const [count, setCount] = useState(0) // const result = calcNumTotal(50) // 1.不依赖任何的值, 进行计算 const result = useMemo(() => { return calcNumTotal(50) }, []) // 2.依赖count // const result = useMemo(() => { // return calcNumTotal(count*2) // }, [count]) // 3.useMemo和useCallback的对比 function fn() {} // const increment = useCallback(fn, []) // const increment2 = useMemo(() => fn, []) // 4.使用useMemo对子组件渲染进行优化 // const info = { name: "why", age: 18 } const info = useMemo(() => ({name: "why", age: 18}), []) return ( <div> <h2>计算结果: {result}</h2> <h2>计数器: {count}</h2> <button onClick={e => setCount(count+1)}>+1</button> <HelloWorld result={result} info={info} /> </div> ) }) export default App
五、Ref/LayoutEffect
1、useRef有两种用法:
(1)绑定DOM
(2)绑定值,解决闭包陷阱
import React, { memo, useRef } from 'react' const App = memo(() => { const titleRef = useRef() const inputRef = useRef() function showTitleDom() { console.log(titleRef.current) inputRef.current.focus() } return ( <div> <h2 ref={titleRef}>Hello World</h2> <input type="text" ref={inputRef} /> <button onClick={showTitleDom}>查看title的dom</button> </div> ) }) export default App
import React, { memo, useRef } from 'react' import { useCallback } from 'react' import { useState } from 'react' let obj = null const App = memo(() => { const [count, setCount] = useState(0) const nameRef = useRef() console.log(obj === nameRef) obj = nameRef // 通过useRef解决闭包陷阱 const countRef = useRef() countRef.current = count const increment = useCallback(() => { setCount(countRef.current + 1) }, []) return ( <div> <h2>Hello World: {count}</h2> <button onClick={e => setCount(count+1)}>+1</button> <button onClick={increment}>+1</button> </div> ) }) export default App
2、useImperativeHandle
import React, { memo, useRef, forwardRef, useImperativeHandle } from 'react' const HelloWorld = memo(forwardRef((props, ref) => { const inputRef = useRef() // 子组件对父组件传入的ref进行处理 useImperativeHandle(ref, () => { return { focus() { console.log("focus") inputRef.current.focus() }, setValue(value) { inputRef.current.value = value } } }) return <input type="text" ref={inputRef}/> })) const App = memo(() => { const titleRef = useRef() const inputRef = useRef() function handleDOM() { // console.log(inputRef.current) inputRef.current.focus() // inputRef.current.value = "" inputRef.current.setValue("哈哈哈") } return ( <div> <h2 ref={titleRef}>哈哈哈</h2> <HelloWorld ref={inputRef}/> <button onClick={handleDOM}>DOM操作</button> </div> ) }) export default App
3、useLayoutEffect
(1)useLayoutEffect和useEffect执行时间不一样
(2)useLayoutEffect在一些情况下可以避免出现屏幕闪烁情况
执行时机:
import React, { memo, useEffect, useLayoutEffect, useState } from 'react' const App = memo(() => { const [count, setCount] = useState(0) useLayoutEffect(() => { console.log("useLayoutEffect") }) useEffect(() => { console.log("useEffect") }) console.log("App render") return ( <div> <h2>count: {count}</h2> <button onClick={e => setCount(count + 1)}>+1</button> </div> ) }) export default App
切换数字useEffect:
import React, { memo, useEffect, useLayoutEffect, useState } from 'react' const App = memo(() => { const [count, setCount] = useState(100) useEffect(() => { console.log("useEffect") if (count === 0) { setCount(Math.random() + 99) } }) console.log("App render") return ( <div> <h2>count: {count}</h2> <button onClick={e => setCount(0)}>设置为0</button> </div> ) }) export default App
切换数字-useLayoutEffect:
import React, { memo, useEffect, useLayoutEffect, useState } from 'react' const App = memo(() => { const [count, setCount] = useState(100) useLayoutEffect(() => { console.log("useLayoutEffect") if (count === 0) { setCount(Math.random() + 99) } }) console.log("App render") return ( <div> <h2>count: {count}</h2> <button onClick={e => setCount(0)}>设置为0</button> </div> ) }) export default App
六、自定义Hooks使用