一、认识和体验Hooks
1. class组件相对于函数式组件的优势
- class组件可以定义自己的state,用来保存组件自己内部的状态;函数式组件每次调用都会产生新的临时变量;
- class组件有自己的生命周期,可以在对应的生命周期中完成自己的逻辑;
- class组件可以在状态改变时只会执行render函数以及生命周期函数componentDidUpdate;函数式组件在重新渲染时,整个函数都会被执行。
2. class组件存在的问题
- 复杂组件变得难以理解;
- 组件复用状态很难。
3. Hook的出现
Hook可以在不编写class的情况下使用state以及其他的React特性
- Hook的使用场景
- Hook的出现基本可以代替我们之前所有使用class组件的地方;
- 如果是一个旧的项目,并不需要直接将所有的代码重构为Hooks,因为它完全向下兼容,你可以渐进式的来使用它;
- Hook只能在函数组件中使用,不能在类组件,或者函数组件之外的地方使用。
- Hook的特点
- 完全可选的;
- 100%向后兼容的;
- 现在可用的;
- 代码变得非常简洁/不用考虑this相关的问题。
4. class组件和函数式组件的对比
- class组件
import React, { PureComponent } from 'react'
export class CounterClass extends PureComponent {
constructor(props) {
super(props)
this.state = {
counter: 0
}
}
increment() {
this.setState({ counter: this.state.counter + 1 })
}
decrement() {
this.setState({ counter: this.state.counter - 1 })
}
render() {
const { counter } = this.state
return (
<div>
<h2>当前计数:{counter}</h2>
<button onClick={e => this.increment()}>+1</button>
<button onClick={e => this.decrement()}>-1</button>
</div>
)
}
}
export default CounterClass
- 函数式组件
import React, { memo, useState } from 'react'
// 普通的函数,里面不能使用hooks
// 在自定义的hooks中,可以使用react提供的其他hooks:必须使用use开头
const CounterHook = memo(() => {
const [counter, setCounter] = useState(0)
return (
<div>
<h2>当前计数:{counter}</h2>
<button onClick={e => setCounter(counter + 1)}>+1</button>
<button onClick={e => setCounter(counter - 1)}>-1</button>
</div>
)
})
export default CounterHook
二、State/Effect
1. useState
- 认识useState
- State Hook的API就是useState,来自react,需要从react中导入;
- 会帮助我们定义一个state变量,useState一种新方法,它与class里面的this.state提供的功能完全相同。
- 参数:初始化值,如果不设置为undefined。
- 返回值:数组,包含两个元素
- 参数一:当前状态的值;
- 参数二:设置状态值的参数。
- 使用规则
- 只能在函数最外层调用Hook。不要在循环、条件判断或者子函数中调用;
- 只能在React的函数组件中调用Hook。不要在其他的JavaScript函数中调用。
- state只在组件首次渲染的时候被创建,在下一次渲染时,useState返回给我们当前的state。
import React, { memo, useState } from 'react'
const App = memo(() => {
const [message, setMessage] = useState("Hello World")
function changeMessage() {
setMessage("你好啊,李银河")
}
return (
<div>
<h2>App: {message}</h2>
<button onClick={changeMessage}>changeText</button>
</div>
)
})
export default App
2. useEffect
- 认识Effect Hook
- 事实上,类似于网络请求、手动更新DOM、一些事件的监听,都是React更新DOM的一些副作用;
- 对于完成这些功能的Hook被称之为Effect Hook;
- Effect Hook可以完成一些类似于class中生命周期的功能。
- useEffect的用法
- 通过useEffect的Hook,可以告诉React需要在渲染后执行某些操作;
- useEffect两个参数:
- 要求我们传入一个回调函数,在React执行完更新DOM操作之后,就会回调这个函数;
- 该useEffect在哪些state发生变化时,才重新执行。
- 默认情况下,无论是第一次渲染,还是每次更新之后,都会执行这个回调函数。
- 清除Effect
- Effect会返回一个函数,这是effect可选的清除机制,每个effect都可以返回一个清除函数。
- 会在组件更新和卸载的时候执行清除操作。
- 使用多个Effect
- 使用Effect Hook,可以将他们分离到不同的useEffect中;
- Hook允许我们按照代码的用途分离它们,而不是像生命周期函数那样。
import React, { memo, useState, useEffect } from 'react'
const App = memo(() => {
const [count, setCount] = useState(0)
// 负责告知react,在执行完当前组件渲染之后要执行的副作用代码
useEffect(() => {
// 1. 修改document的title
console.log("修改title")
})
// 一个函数式组件中,可以存在多个useEffect
useEffect(() => {
// 2. 对redux中数据变化监听
console.log("监听redux中的数据")
return () => {
// 取消redux中数据的监听
}
}, [])
useEffect(() => {
// 3. 监听eventBus中的why事件
console.log("监听eventBus的why事件")
return () => {
// 取消eventBus中的why事件监听
}
}, [])
useEffect(() => {
console.log("发送网络请求,从服务器中获取数据")
return () => {
console.log("会在组件被卸载时,才会执行一次")
}
}, [])
return (
<div>
<button onClick={e => setCount(count+1)}>+1({count})</button>
</div>
)
})
export default App
Hook指的类似于useState、useEffect这样的函数;Hooks是对这类函数的统称。
三、Context/Reducer
1. useContext
- 类组件使用共享的Context的两种方式
- 类组件可以通过 类名.contextType = MyContext 方式,在类中获取context;
- 多个context或者在函数式组件中通过MyContext.Consumer方式共享context。
- Context Hook
- Context Hook允许我们通过Hook来获取某个Context的值。
// --- index.js ---
import React from 'react';
import ReactDOM from 'react-dom/client';
import { UserContext, ThemeContext } from "./context"
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<UserContext.Provider value={{name: "why", level: 99}}>
<ThemeContext.Provider value={{color: "red", size: 30}}>
<App />
</ThemeContext.Provider>
</UserContext.Provider>
);
// --- context/index.js ---
import { createContext } from "react";
const UserContext = createContext()
const ThemeContext = createContext()
export {
UserContext,
ThemeContext
}
// --- App.jsx ---
import React, { memo, useContext } from 'react'
import { UserContext, ThemeContext } from "./context"
const App = memo(() => {
// 使用Context
const user = useContext(UserContext)
const theme = useContext(ThemeContext)
return (
<div>
<h2>User: {user.name}-{user.level}</h2>
<h2 style={{color: theme.color, fontSize: theme.size}}>Theme</h2>
</div>
)
})
export default App
2. useReducer
- useReducer仅仅是useState的一种替代方案:
- 在某些场景下,如果state的处理逻辑比较复杂,我们可以通过useReducer来对其进行拆分;
- 或者这次修改的state需要依赖之前的state时,也可以使用。
import React, { memo, useReducer } 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
}
}
const App = memo(() => {
const [state, dispatch] = useReducer(reducer, { counter: 0})
return (
<div>
<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
1. useCallback
- useCallback实际的目的是为了进行性能的优化。
- 如何进行性能优化:
- useCallback会返回一个函数的记忆的值;
- 在依赖不变的情况下,多次定义的时候,返回的值是相同的。
import React, { memo, useState, useCallback, useRef } from 'react'
const HYIncrement = memo((props) => {
const { increment } = props
return (
<div>
<button onClick={increment}>increment+1</button>
</div>
)
})
const App = memo(() => {
const [count, setCount] = useState(0)
// 做法一:将count依赖移除掉,缺点:闭包陷阱
// const increment = useCallback(function foo() {
// setCount(count + 1)
// }, [count])
// 做法二:useRef,在组件多次渲染时,返回的是同一个值
const countRef = useRef()
countRef.current = count
const increment = useCallback(function foo() {
console.log("increment")
setCount(countRef.current + 1)
}, [])
return (
<div>
<h2>计数:{count}</h2>
<button onClick={increment}>+1</button>
<HYIncrement increment={increment}/>
</div>
)
})
export default App
2. useMemo
- useMemo实际的目的也是为了性能的优化。
- 如何进行性能优化:
- useCallback返回也是一个函数的记忆的值;
- 在依赖不变的情况下,多次定义的时候,返回的值是相同的。
import React, { memo, useCallback, useMemo, useState } from 'react'
const HelloWorld = memo(function(props) {
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)
// 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 info={info} />
</div>
)
})
export default App
五、Ref/LayoutEffect
1. useRef
- useRef返回的是ref对象,返回的ref对象在组件的整个生命周期保持不变。
- 最常用的两种用法:
- 用法一:引入DOM元素;
- 用法二:保存一个数据,这个对象在整个生命周期中可以保存不变。
import React, { memo, useState, useRef } from 'react'
const App = memo(() => {
const [count, setCount] = useState(0)
// 1. 引入DOM元素
const inputRef = useRef()
function showTitleDom() {
inputRef.current.focus()
}
// 2. 使用ref保存某值
const countRef = useRef()
countRef.current = count
const increment = useCallback(() => {
setCount(countRef.current + 1)
}, [])
return (
<div>
<input type="text" ref={inputRef} />
<button onClick={showTitleDom}>查看title的dom</button>
<h2>Hello World: {count}</h2>
<button onClick={e => setCount(count+1)}>+1</button>
<button onClick={increment}>+1</button>
</div>
)
})
export default App
2. useImperativeHandle
- 通过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() {
inputRef.current.focus()
inputRef.current.setValue("hahaha")
}
return (
<div>
<h2 ref={titleRef}>哈哈哈</h2>
<HelloWorld ref={inputRef}/>
<button onClick={handleDOM}>DOM操作</button>
</div>
)
})
export default App
3. useLayoutEffect
- useEffect和useLayoutEffect的区别:
- useEffect会在渲染的内容更新到DOM上后执行,不会阻塞DOM的更新;
- useLayoutEffect会在渲染的内容更新到DOM上之前执行,会阻塞DOM的更新。
import React, { memo, useLayoutEffect,useEffect, 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
六、Redux中的hooks
1. useSelector
- useSelector的作用是将state映射到组件上。
- 两个参数:
- 将state映射到需要的数据中;
- 可以进行比较来决定是否组件重新渲染(shallowEqual可进行浅层比较)。
2. useDispatch
- useDispatch就是直接获取dispatch函数,之后在组件中直接使用即可。
import React, { memo } from 'react'
import { useSelector, useDispatch, shallowEqual } from "react-redux"
import { addNumberAction, changeMessageAction, subNumberAction } from './store/modules/counter'
const App = memo((props) => {
// 1. 使用useSelector将redux中store的数据映射到组件内
const { count } = useSelector((state) => ({
count: state.counter.count
}), shallowEqual)
// 2. 使用dispatch直接派发action
const dispatch = useDispatch()
function addNumberHandle(num, isAdd = true) {
if (isAdd) {
dispatch(addNumberAction(num))
} else {
dispatch(subNumberAction(num))
}
}
return (
<div>
<h2>当前计数:{count}</h2>
<button onClick={e => addNumberHandle(6)}>+6</button>
<button onClick={e => addNumberHandle(6, false)}>-6</button>
<Home/>
</div>
)
})
export default App