1.什么是Hook?
- Hook是React 16.8的新增特性
- 可以让函数式组件拥有类组件特性
2.为什么需要Hook?
- 在Hook出现之前, 如果我们想在组件中保存自己的状态,
如果我们想在组件的某个生命周期中做一些事情, 那么我们必须使用类组件
- 但是类组件的学习成本是比较高的, 你必须懂得ES6的class, 你必须懂得箭头函数
- 但是在类组件的同一个生命周期方法中, 我们可能会编写很多不同的业务逻辑代码 这样就导致了大量不同的业务逻辑代码混杂到一个方法中, 导致代码变得很难以维护 (诸如: 在组件被挂载的生命周期中, 可能主要注册监听, 可能需要发送网络请求等)
- 但是在类组件中共享数据是非常繁琐的, 需要借助Context或者Redux等
- 所以当应用程序变得复杂时, 类组件就会变得非常复杂, 非常难以维护
- 所以Hook就是为了解决以上问题而生的
3.如何使用Hook?
- Hook的使用我们无需额外安装任何第三方库, 因为它就是React的一部分
- Hook只能在函数组件中使用, 不能在类组件,或者函数组件之外的地方使用
- Hook只能在函数最外层调用, 不要在循环、条件判断或者子函数中调用
2.Hooks使用
useState
- 参数:保证状态的初始值
- 返回值: 数组(两个元素)
- 第一个元素:保存的状态
- 第二个元素:修改保存状态的方法
注意点:在同一个函数式组件中,可以多次使用同名的Hook
import React, { useState } from "react"
function About() {
const arr = useState(777)
const [list, setList] = useState([
{
id: 1, name: '花花'
},
{
id: 2, name: '萌兰'
},
{
id: 3, name: '飞云'
}
])
const [state, setState] = arr
return (
<div>
<p>{state}</p>
<button onClick={() => setState(state + 1)}>+1</button>
<button onClick={() => setState(state - 1)}>-1</button>
<ul>
{
list.map(item => {
return <li>{item.name}</li>
})
}
</ul>
</div>
)
}
export default About
useEffect
- 功能
可以把
useEffect
Hook看做 componentDidMount、componentDidUpdate和componentWillUnmount三个周期函数的组合
- 特点
- 可以设置依赖,只有
依赖发生变化
的时候才会执行 - 对比类组件优势,易于拆分
- 可以设置依赖,只有
import React, { useEffect, useState } from "react"
function About() {
const arr = useState(777)
const [state, setState] = arr
// useEffect(() => {
// console.log('组件被挂载或者组件更新完成')
// return () => {
// console.log('组件即将被卸载')
// }
// })
useEffect(() => {
console.log('修改DOM')
})
useEffect(() => {
// 组件被挂载
console.log('注册监听')
return () => {
console.log('移出监听')
}
})
useEffect(() => {
console.log('发送网络请求')
})
return (
<div>
<p>{state}</p>
<button onClick={() => setState(state + 1)}>+1</button>
<button onClick={() => setState(state - 1)}>-1</button>
</div>
)
}
export default About
useContext
useContext相当于 类组件中的 static contextType = Context
import React, { createContext, useContext, useState } from "react"
const UserConetext = createContext({})
const ColorContext = createContext({})
function ASon() {
const user = useContext(UserConetext)
const color = useContext(UserConetext)
return (
<div>
<p>{user.name}</p>
<p>{user.age}</p>
<p>{color.color}</p>
</div>
)
}
function About() {
const arr = useState(777)
const [state, setState] = arr
return (
<div>
<p>{state}</p>
<button onClick={() => setState(state + 1)}>+1</button>
<button onClick={() => setState(state - 1)}>-1</button>
<UserConetext.Provider value={{ name: 'ss', age: state }}>
<ColorContext.Provider value={{ color: 'red' }}>
<ASon />
</ColorContext.Provider>
</UserConetext.Provider>
</div>
)
}
export default About
useReducer
从名称来看, 很多人会误以为useReducer是用来替代Redux的, 但是其实不是。seReducer是useState的一种替代方案, 可以让我们很好的复用操作数据的逻辑代码
import React, { useState } from "react"
function ASon() {
const [numState, setNumState] = useState(0)
function increment() {
setNumState(numState + 1)
}
function decrement() {
setNumState(numState - 1)
}
return (
<div>
<p>{numState}</p>
<button onClick={() => increment()}>+1</button>
<button onClick={() => decrement()}>-1</button>
</div>
)
}
function BSon() {
const [numState, setNumState] = useState(0)
function increment() {
setNumState(numState + 1)
}
function decrement() {
setNumState(numState - 1)
}
return (
<div>
<p>{numState}</p>
<button onClick={() => increment()}>+1</button>
<button onClick={() => decrement()}>-1</button>
</div>
)
}
function About() {
return (
<div>
<ASon />
<BSon />
</div>
)
}
export default About
上面代码的问题:重复性代码,冗余
import React, { useState, useReducer } from "react"
function reducer(state, action) {
switch (action.type) {
case 'ADD':
return { ...state, num: state.num + 1 }
case 'SUB':
return { ...state, num: state.num - 1 }
default:
return state
}
}
function ASon() {
const [state, dispatch] = useReducer(reducer, { num: 0 })
return (
<div>
<p>{state.num}</p>
<button onClick={() => dispatch({ type: 'ADD' })}>+1</button>
<button onClick={() => dispatch({ type: 'SUB' })}>-1</button>
</div>
)
}
function BSon() {
const [state, dispatch] = useReducer(reducer, { num: 5 })
return (
<div>
<p>{state.num}</p>
<button onClick={() => dispatch({ type: 'ADD' })}>+1</button>
<button onClick={() => dispatch({ type: 'SUB' })}>-1</button>
</div>
)
}
function About() {
return (
<div>
<ASon />
<BSon />
</div>
)
}
export default About
useCallback
1.什么是useCallback
Hook?
useCallback用于优化代码, 可以让对应的函数只有在
依赖
发生变化时才重新定义
- 当前ASon和BSon重新渲染的原因是因为
- 父组件中的数据发生了变化, 会重新渲染父组件
- 重新渲染父组件, 就会重新执行父组件函数
- 重新执行父组件函数, 就会重新定义increment/decrement
- 既然increment/decrement是重新定义的, 所以就和上一次的不是同一个函数了
- 既然不是同一个函数, 所以ASon和BSon接收到的内容也和上一次的不一样了
- 既然接收到的内容和上一次不一样了, 所以就会重新渲染
问题一
import React, { useState } from "react"
function ASon() {
console.log('ASON渲染')
return (
<div>
<p>ason</p>
</div>
)
}
function BSon() {
console.log('BSON渲染')
return (
<div>
<p>bson</p>
</div>
)
}
function About() {
const [num, setNum] = useState(0)
function increment() {
setNum(num + 1)
}
return (
<div>
<>{num}</>
<button onClick={() => { increment() }}>增加</button>
<ASon />
<BSon />
</div>
)
}
export default About
父改变,子会重新渲染
解决:使用memo
memo
React.memo
仅检查 props 变更
官网说明
import React, { useState, memo } from "react"
function ASon() {
console.log('ASON渲染')
return (
<div>
<p>ason</p>
</div>
)
}
function BSon() {
console.log('BSON渲染')
return (
<div>
<p>bson</p>
</div>
)
}
const MemoASon = memo(ASon)
const MemoBSon = memo(BSon)
function About() {
const [num, setNum] = useState(0)
function increment() {
setNum(num + 1)
}
return (
<div>
<>{num}</>
<button onClick={() => { increment() }}>增加</button>
<MemoASon />
<MemoBSon />
</div>
)
}
export default About
问题2
import React, { useState, memo, useCallback } from "react"
function ASon(props) {
console.log('ASON渲染')
return (
<div>
<p>ason</p>
<button onClick={() => props.handler()}>+</button>
</div>
)
}
function BSon(props) {
console.log('BSON渲染')
return (
<div>
<p>bson</p>
<button onClick={() => props.handler()}>-</button>
</div>
)
}
const MemoASon = memo(ASon)
const MemoBSon = memo(BSon)
function About() {
console.log('ABOUT渲染')
const [num, setNum] = useState(0)
const [countState, setCountState] = useState(0)
function increment() {
setNum(num + 1)
}
function decrement() {
setCountState(countState - 1)
}
return (
<div>
<div>num: {num}</div>
<div>countState: {countState}</div>
{/* <button onClick={() => { increment() }}>增加</button> */}
<MemoASon handler={increment} />
<MemoBSon handler={decrement} />
</div>
)
}
export default About
子改父,子未改变,但是父改变,传入的props内容改变,子全部重新渲染
解决:useCallback
useCallback
import React, { useState, memo, useCallback } from "react"
function ASon(props) {
console.log('ASON渲染')
return (
<div>
<p>ason</p>
<button onClick={() => props.handler()}>+</button>
</div>
)
}
function BSon(props) {
console.log('BSON渲染')
return (
<div>
<p>bson</p>
<button onClick={() => props.handler()}>-</button>
</div>
)
}
const MemoASon = memo(ASon)
const MemoBSon = memo(BSon)
function About() {
console.log('ABOUT渲染')
const [num, setNum] = useState(0)
const [countState, setCountState] = useState(0)
function increment() {
setNum(num + 1)
}
// function decrement() {
// setCountState(countState - 1)
// }
const decrement = useCallback(() => {
setCountState(countState - 1)
}, [countState]) // 只有依赖数据countState改变才会重新渲染定义,否则函数decrement定义不变
return (
<div>
<div>num: {num}</div>
<div>countState: {countState}</div>
{/* <button onClick={() => { increment() }}>增加</button> */}
<MemoASon handler={increment} />
<MemoBSon handler={decrement} />
</div>
)
}
export default About
useMemo
useMemo用于优化代码, 可以让对应的函数只有在
依赖
发生变化时才返回新的值
import {memo, useCallback, useMemo, useState} from "react"
function ASon (prop) {
console.log('ASon渲染');
return (
<div>
<div>ASon</div>
<button onClick={() => prop.handle()}>+</button>
</div>
)
}
function BSon (prop) {
console.log('BSon渲染');
return (
<div>
<div>BSon</div>
<button onClick={() => prop.handle()}>-</button>
</div>
)
}
const ASonMemo = memo(ASon)
const BSonMemo = memo(BSon)
function About () {
console.log('About渲染');
const [num, setNum] = useState(5)
const [aboutState, setAboutState] = useState(0)
function increment() {
setNum(num + 1)
}
// function decrement() {
// setAboutState(aboutState - 1)
// }
// const decrement = useCallback(() => {
// setAboutState(aboutState - 1)
// }, [aboutState])
const decrement = useMemo(() => {
return () => {
setAboutState(aboutState - 1)
}
}, [aboutState])
return (
<div>
<div>About</div>
<div>{num}</div>
<div>{aboutState}</div>
<ASonMemo handle={increment}></ASonMemo>
<BSonMemo handle={decrement}></BSonMemo>
</div>
)
}
export default About
useCallback实现原理
// 伪代码
function useCallback(fn, arr){
return useMemo(()=>{
return fn;
}, arr);
}
useCallback和useMemo的区别
useCallback
返回的永远是一个函数
useMemo
返回的是return返回的内容
import {memo, useCallback, useMemo, useState} from "react"
function ASon (prop) {
console.log('ASon渲染');
return (
<div>
<div>ASon</div>
<button onClick={() => prop.handle()}>+</button>
</div>
)
}
function BSon (prop) {
console.log('BSon渲染');
return (
<div>
<div>BSon</div>
{/* <button onClick={() => prop.handle()}>-</button> */}
<>{prop.user.name}</>-<>{prop.user.age}</>
</div>
)
}
const ASonMemo = memo(ASon)
const BSonMemo = memo(BSon)
function About () {
console.log('About渲染');
const [num, setNum] = useState(5)
const [aboutState, setAboutState] = useState(0)
function increment() {
setNum(num + 1)
}
const user = useMemo(() => {
return {name: 'user1', age: 18}
}, [])
return (
<div>
<div>About</div>
<div>{num}</div>
<div>{aboutState}</div>
<ASonMemo handle={increment}></ASonMemo>
<BSonMemo user={user}></BSonMemo>
</div>
)
}
export default About
依赖内容为空,说明不会改变,其他组件(About、ASon)的重新渲染,不会对使用该数据的组件(BSon)造成影响
import React, {useState, useMemo} from 'react';
/*
1.什么是useMemo Hook?
useMemo用于优化代码, 可以让对应的函数只有在依赖发生变化时才返回新的值
* */
// 定义一个函数, 模拟耗时耗性能操作
function calculate() {
console.log('calculate被执行了');
let total = 0;
for(let i = 0; i < 999; i++){
total += i;
}
return total;
}
function App() {
console.log('App被渲染了');
const [numState, setNumState] = useState(0);
// const total = calculate();
const total = useMemo(()=>{
return calculate();
}, []);
return (
<div>
<p>{total}</p>
<p>{numState}</p>
<button onClick={()=>{setNumState(numState + 1)}}>增加</button>
</div>
)
}
export default App;
useRef
- useRef是createRef的Hook,比createRef更强大
- useRef可以用来获取元素【和createRef一样不能直接获取函数式组件】、保存数据
- useRef和useState的区别:useRef中保存的数据,除非手动修改,否则永远都不会发生变化
上面的报错:【获取元素】直接获取函数式组件
import {memo, createRef, useEffect, useRef, useState} from "react"
function ASon (prop) {
return (
<div>
<div>ASon</div>
</div>
)
}
function BSon (prop) {
return (
<div>
<div>BSon</div>
</div>
)
}
const ASonMemo = memo(ASon)
const BSonMemo = memo(BSon)
function About () {
const ason = createRef()
const bson = useRef() // 获取元素
const data = useRef(0) // 保存数据
const [numState, setNumState] = useState(0)
const refData = useRef(numState)
useEffect(() => {
console.log('ason', ason.current);
console.log('bson', bson.current);
console.log('data', data.current)
})
useEffect(() => {
console.log('改变了')
refData.current = numState
}, [numState]) // 依赖发生变化才会执行
return (
<div>
<div ref={ason}>ason</div>
<div ref={bson}>bson</div>
{/* createRef不能获取函数组件 */}
{/* <ASonMemo ref={ason}></ASonMemo> */}
<div>numState: {numState}</div>
<div>refData: {refData.current}</div>
<button onClick={()=>setNumState(numState + 1)}>change</button>
<ASonMemo></ASonMemo>
<BSonMemo ></BSonMemo>
</div>
)
}
export default About
useImperativeHandle
- 可以在使用ref时自定义暴露给父组件的实例值【权限控制】
不使用useImperativeHandle
可以直接操作暴露的元素的内容
import { useEffect, useRef, forwardRef} from "react"
function ASon (prop) {
return (
<div>
<div>ASon</div>
</div>
)
}
function BSon (props, fason) {
return (
<div>
<div>BSon</div>
<input ref={fason} />
</div>
)
}
const ForwardASon = forwardRef(BSon)
function About () {
const fason = useRef()
useEffect(() => {
console.log('fason', fason)
fason.current.focus()
})
function setInput() {
fason.current.value = '123'
}
return (
<div>
{/* <div ref={bson}>bson</div> */}
{/* createRef不能获取函数组件 */}
{/* <ASonMemo ref={ason}></ASonMemo> */}
<ForwardASon ref={fason}></ForwardASon>
<button onClick={() => setInput()}>input</button>
</div>
)
}
export default About
使用useImperativeHandle
可以对需要操作的内容进行控制
import { useEffect, useRef, forwardRef, useImperativeHandle} from "react"
function ASon (prop) {
return (
<div>
<div>ASon</div>
</div>
)
}
function BSon (props, fason) {
const inputRef = useRef()
useImperativeHandle(fason, () => {
return {
myFocus: () => {
inputRef.current.focus()
},
setInput: () => {
inputRef.current.value = '123'
}
}
})
return (
<div>
<div>BSon</div>
<input ref={inputRef} />
</div>
)
}
const ForwardASon = forwardRef(BSon)
function About () {
const fason = useRef()
useEffect(() => {
console.log('fason', fason)
fason.current.myFocus()
})
function setInput() {
// fason.current.value = '123'
fason.current.setInput()
}
return (
<div>
{/* <div ref={bson}>bson</div> */}
{/* createRef不能获取函数组件 */}
{/* <ASonMemo ref={ason}></ASonMemo> */}
<ForwardASon ref={fason}></ForwardASon>
<button onClick={() => setInput()}>input</button>
</div>
)
}
export default About
useLayoutEffect
需要修改DOM的布局样式,推荐使用useLayoutEffect
- useEffect函数会在组件渲染到屏幕之前才执行,所以可能会出现闪屏
- useLayoutEffect函数是在组件渲染到屏幕之前执行的,所以不会出现闪屏
为什么要使用useLayoutEffect来更新DOM布局和样式?
- useEffect是组件已经渲染到屏幕上了才执行
- useLayoutEffect是组件还没有渲染到屏幕上就会执行
- 所以如果在组件已经渲染到屏幕上了, 才去更新DOM的布局和样式, 那么用户体验不好, 会看到闪屏的情况
- 而如果是在组件还没有渲染到屏幕上, 就去更新DOM的布局和样式, 那么用户体验更好, 看不到闪屏情况
useEffect useLayoutEffect区别
- 执行时间不同
- 如果是挂载或更新组件,那么useLayoutEffect会比useEffect先执行
- 如果是挂载组件,那么useEffect会比useLayoutEffect先执行
自定义hook
- 通过自定义 Hook,可以对其它Hook的代码进行复用
注意点: 在React中只有两个地方可以使用Hook
- 函数式组件中
- 自定义Hook中
如何自定义一个Hooks
只要在函数名称前面加上
use
, 那么就表示这个函数是一个自定义Hook
, 就表示可以在这个函数中使用其它的Hook
import React, {useEffect, useState} from 'react';
import './app.css'
/*
1.什么是自定义 Hook?
通过自定义 Hook,可以对其它Hook的代码进行复用
* */
/*
注意点: 在React中只有两个地方可以使用Hook
- 函数式组件中
- 自定义Hook中
如何自定义一个Hooks
只要在函数名称前面加上use, 那么就表示这个函数是一个自定义Hook, 就表示可以在这个函数中使用其它的Hook
* */
function useAddListenr(name) {
useEffect(()=>{
console.log(name, ' - 组件被挂载或者更新完成 -- 添加监听');
return ()=>{
console.log(name, ' - 组件即将被卸载 -- 移出监听');
}
});
}
function Home() {
/*
useEffect(()=>{
console.log('Home - 组件被挂载或者更新完成 -- 添加监听');
return ()=>{
console.log('Home - 组件即将被卸载 -- 移出监听');
}
});
*/
useAddListenr('Home');
return (
<div>Home</div>
)
}
function About() {
/*
useEffect(()=>{
console.log('About - 组件被挂载或者更新完成 -- 添加监听');
return ()=>{
console.log('About - 组件即将被卸载 -- 移出监听');
}
});
*/
useAddListenr('About');
return (
<div>About</div>
)
}
function App() {
const [show, setShow] = useState(true);
return (
<div>
{show && <Home/>}
{show && <About/>}
<button onClick={()=>{setShow(!show)}}>切换</button>
</div>
)
}
export default App;
import React, {createContext, useContext} from 'react';
/*
1.什么是自定义 Hook?
通过自定义 Hook,可以对其它Hook的代码进行复用
* */
const UserContext = createContext({});
const InfoContext = createContext({});
function useGetContext() {
// 在企业开发中, 但凡需要抽取代码, 但凡被抽取的代码中用到了其它的Hook,
// 那么就必须把这些代码抽取到自定义Hook中
const user = useContext(UserContext);
const info = useContext(InfoContext);
return [user, info]
}
function Home() {
// const user = useContext(UserContext);
// const info = useContext(InfoContext);
const [user, info] = useGetContext();
return (
<div>
<p>{user.name}</p>
<p>{user.age}</p>
<p>{info.gender}</p>
<hr/>
</div>
)
}
function About() {
// const user = useContext(UserContext);
// const info = useContext(InfoContext);
const [user, info] = useGetContext();
return (
<div>
<p>{user.name}</p>
<p>{user.age}</p>
<p>{info.gender}</p>
<hr/>
</div>
)
}
function App() {
return (
<UserContext.Provider value={{name:'lnj', age:18}}>
<InfoContext.Provider value={{gender:'man'}}>
<Home/>
<About/>
</InfoContext.Provider>
</UserContext.Provider>
)
}
export default App;
学习笔记