目录
b、useEffect 传入的回调函数的返回值 (清除操作)
d、useEffect 的两个参数(effect 的性能优化)
2、在函数式组件中使用useSelector 和 useDispatch
一、类组件和函数式组件的对比
Hook 的特性:在编写class 的情况下,使用state 以及其他React 特性(比如生命周期)
类组件相比于函数式组件的优势:
- 类组件可以定义自己的state,用来保存组件自己内部的状态。函数式组件不可以,因为函数每次调用都会产生新的临时变量
- 类组件有自己的生命周期,可以在对应的生命周期中完成自己的逻辑。比如在componentDidMount 中发送网络请求,并且该声明周期函数只会执行一次。函数式组件在函数中发送网络请求,意味着每次重新渲染都会重新发送一次网络请求。
- 类组件可以在状态改变时只会重新执行render 函数,以及重新调用的声明周期函数componentDidUpdate 等,函数式组件在重新渲染时,整个函数都会被执行
二、Hooks 的使用
1、useState
- 在函数退出后函数中定义的变量会“消失”, 而state 中的变量会被React 保留
- useState 接收唯一一个参数,在第一次组件被调用时使用并作为初始值(如果没有传递参数,那么初始值为undefined)
- useState 的返回值是一个数组,可以通过解构获取state 和setState
import { memo, useState } from "react"
function CounterHook(props) {
const [ counter, setCounter ] = useState(0)
function decrement() {
setCounter(counter - 1)
}
return (
<div>
<h2>当前计数: {counter}</h2>
<button onClick={e=>setCount(count+1)}>+1</button>
<button onClick={decrement}>-1</button>
</div>
)
}
export default memo(CounterHook)
2、useEffect
a、useEffect 的基本使用
- useEffect 传入的回调函数会在组件被渲染完成后,会自动执行
- effect 在每次渲染的时候都会执行
// 在组件第一次渲染、组件更新时都会执行
useEffect(() => {
// 当前传入的回调函数会在组件被渲染完成后,会自动执行
// 网络请求/DOM操作/事件监听
})
b、useEffect 传入的回调函数的返回值 (清除操作)
- useEffect 传入的回调函数可以有一个返回值,这个返回值是另外一个回调函数。这是effect 可选的清除机制,每个effect 都可以返回一个清除函数,这样就可以将添加和移除订阅的逻辑放在一起
- react 会在组件更新和卸载的时候执行清除操作
useEffect(() => {
console.log("")
// 回调函数:回调函数 => 组件被重新渲染或者组件卸载的时候执行
return () => {
console.log("")
}
})
c、一个函数式组件可以有多个useEffect
useEffect(() => {
console.log("修改state")
})
useEffect(() => {
console.log("监听redux 中的数据")
return () => {
// 取消redux 中数据的监听
}
})
d、useEffect 的两个参数(effect 的性能优化)
- 参数一:执行的回调函数
- 参数二:该useEffect 在哪些state 发生变化时,才重新执行(受谁的影响)
e、useEffect 的第二个参数是空数组时
- 当useEffect 的第二个参数是空数组时,只在组件第一次渲染时执行
- 类似于类组件的 componentDidMount
- 如果useEffect 的第一个参数回调函数有返回值时,只会在组件卸载时执行
- 类似于类组件的componentWillUnmount
useEffect(() => {
console.log("在组件第一次渲染时执行一次")
return () => {
console.log("在组件卸载时执行一次")
}
}, [])
f、useEffect 的第二个参数不是空数组时
- useEffect 的第二个参数是数组,并且数组中有依赖的值
- useEffect 会在组件第一次渲染时执行一次
- 并且当依赖的值发生变化时,useEffect 会自动执行
- 该useEffect 在哪些state 发生变化时,才重新执行(受谁的影响)
useEffect(() => {
console.log("count 发生了变化")
}, [count])
g、第二个参数的对比
useEffect(() => {
console.log("在组件第一次渲染和组件每次重新渲染时之行")
})
useEffect(() => {
console.log("只在组件第一次渲染时执行")
}, [])
useEffect(() => {
console.log("在组件第一次渲染和count 发生变化时执行")
}, [count])
useEffect(() => {
console.log("在组件第一次渲染和count 或setCount 发生变化时执行")
}, [count, setCount])
3、useContext
组件中使用共享的Context 有两种方式:
- 类组件可以通过类名contextType = MyContext 方式,在类中获取context
- 多个Context 或者在函数式组件中通过MyContext.Consumer 方式共享context
注意事项:
- 当组件上层最近的<MyContext.Provider> 更新时,该Hook 会触发重新渲染,并使用最新传递给MyContext provider 的context value 值
// src/context/index.js
import { createContext } from "react"
const UserContext = createContext()
const ThemeContext = createContext()
export {
UserContext,
ThemeCoontext
}
// src/index.js
import { UserContext, ThemeContext } from "@/context/index.js"
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<UserContext.Provider value={{ name: "why", age: 18 }}>
<ThemeContext.Provider value={{ color: "red", size: 30 }}>
<App/>
</ThemeContext.Provider>
</UserContext.Provider>
);
// src/App.jsx
import { memo, useContext } from 'react'
import { UserContext, ThemeContext } from "@/context/index.js"
const App = memo(() => {
// 使用context
const user = useContext(UserContext)
const theme = useContext(ThemeContext)
return (
<div>
<h2>{ user.name } - { user.age }</h2>
<div style={{ color: theme.color, fontSize: theme.size }}>主题</div>
</div>
)
})
export default App
4、useReducer(了解)
- useReducer 是useState 的一种替代方案
- 在某些情况下,如果state 的处理逻辑比较复杂时,可以通过useReducer 来对其进行拆分
- 或者这次修改的state 需要依赖之前的state 时,也可以使用
import { 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 }
}
}
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>
</div>
)
})
5、useCallback(性能优化)
- useCallback 的第一个参数是函数,第二个参数表示对谁的依赖
- useCallback 性能优化:当需要将一个函数传递给子组件时,最好使用useCallback 进行优化,将优化之后的函数传递给子组件
- 在依赖不变的情况下,多次定义的时候,返回的值是相同的
当修改count 时,会影响子组件HYHome 重新渲染,当修改message 时,子组件不重新渲染
import { useCallback, useRef } from 'react'
const HYHome = memo(function(props) {
// props 中的属性发生改变时,组件本身就会被重新渲染
const { increment } = props
console.log("HYHome 被渲染")
return (
<div>
<button onClick={increment}>increment + 1</button>
</div>
)
})
const App = memo(function() {
const [count, setCount] = useState(0)
const [message, setMessage] = useState("hello")
// 1. 闭包陷阱
const increment = useCallback(function foo() {
console.log("increment")
setCount(count + 1)
}, [count])
// 2. 使用useRef 实现
const countRef = useRef()
countRef.current = count
const increment = useCallback(function foo() {
console.log("inncrement")
setCount(countRef.current + 1)
}, [])
// 3. 普通的函数
const increment = () => {
setCount(count +1)
}
return (
<div>
<h2>计数: {count}</h2>
<button onClick={increment}>+1</button>
<HYHome increment={increment}/>
<h2>{ message }</h2>
<button onCick={e => setMessage(Math.random())}>修改message</button>
</div>
)
})
6、useMemo
- useMemo 的第一个参数是值,而不是函数
- useMemo 的第二个参数表示对谁做依赖
- 当第二个参数为空数组时,表示对谁都不依赖,只是在组件第一次渲染时执行一次
- 如果不使用useMemo, 使用setCount 修改count 的值时,calcNumTotal 函数也会被调用
import { useMemo, useState } from 'react'
function calcNumTotal(num) {
console.log("函数被调用")
let total = 0;
for(let i = 0; i <= num; i++){
total += i
}
return total
}
const App = memo(() => {
const [count, setCount] = useState(0)
// useMemo 的第二个参数表示对谁做依赖
// 当第二个参数为空数组时,表示对谁都不依赖,只是在组件第一次渲染时执行一次
// 1、不依赖任何值
const result = useMemo(() => {
return calcNumTotal(50)
// useMemo 的第二个参数表示对谁做依赖
// 当第二个参数为空数组时,表示对谁都不依赖,只是在组件第一次渲染时执行一次
}, [])
// 2、依赖count
// const result = useMemo(() => {
// return calcNumTotal(50)
// }, [count])
return (
<div>
<h2>计算结果: {calcNumTotal(50)}</h2>
<h2>{count}</h2>
<button onClick={e => setCount(count + 1)}>+1</button>
</div>
)
})
- 对子组件传递相同内容的对象时,使用useMemo 可以进行性能优化
import { useMemo, useState } from 'react'
function calcNumTotal(num) {
console.log("函数被调用")
let total = 0;
for(let i = 0; i <= num; i++){
total += i
}
return total
}
const SubApp = memo(() => {
console.log("子组件被渲染")
return <h2>子组件被渲染</h2>
})
const App = memo(() => {
const [count, setCount] = useState(0)
// 子组件不会重新渲染
const info = useMemo(() => ({name: "why", age: 18}), [])
// 子组件会重新渲染
// const info = { name: "why", age: 18 }
return (
<div>
<h2>计算结果: {calcNumTotal(50)}</h2>
<h2>{count}</h2>
<button onClick={e => setCount(count + 1)}>+1</button>
<SubApp info={info}/>
</div>
)
})
7、useCallback 和 useMemo 的比较
a、区别
- useCallback 的第一个参数是函数
- useMemo 的第一个参数是值,可以是函数返回一个值
b、相同点
- 使用useCallback 和useMemo 在定义一个函数时不会带来性能优化
- 使用useCallback 和useMemo 在定义一个函数传递给子组件会带来性能优化
- 使用useCallback 和useMemo 的目的是不希望子组建进行多次渲染,而不是为了函数进行缓存
const increment = useCallback(() => {}, [])
const decrement = useMemo(() => {
return
}, [])
// 即:
const increment = useCallback(fn, [])
const decrement = useMemo(() => fn, [])
8、useRef
- useRef 返回的是一个ref 对象,返回的ref 对象在组件的整个生命周期保持不变
- ref 的两个用法:
- 引入DOM (或者组件,但是需要是class 组件)元素
- 保存一个数据,这个对象在整个生命周期中可以保持不变
a、ref绑定DOM元素
import React, { memo, useRef } from 'react'
const App = memo(() => {
const titleRef = useRef()
function showTitleDom() {
console.log(titleRef.current)
}
return (
<div>
<h2 ref={titleRef}>Hello World</h2>
<button onClick={ showTitleDom }>查看title 的dom</button>
</div>
)
})
b、ref 绑定子组件中的元素
import React, {memo, useRef, forwardRef} from 'react'
const HelloWorld = memo(forwardRef((props, ref) => {
return <input type="text" ref={ref} />
}))
const App = memo(() => {
const inputRef = useRef()
function handleDom() {
console.log(inputRef.current)
inputRef.current.focus()
}
return (
<div>
<HelloWorld ref={inputRef}/>
<button onClick={handleDom}>DOM操作</button>
</div>
)
})
c、使用useRef 解决闭包陷阱
const App = memo(() => {
const [count, setCount] = useState(0)
const countRef = useRef()
countRef.current = count
const increment = useCallback(() => {
// 1、会产生闭包陷阱,获取的count 一直是0
// setCount(count + 1)
// 2、解决闭包陷阱
setCount(countRef.current + 1)
}, [])
return (
<div>
<button onClick={increment}>+1</button>
</div>
)
})
9、useImperativeHandle
对ref 的操作可以自定义方法进行控制
10、useLayoutEffect
- useEffect 会在渲染的内容更新到DOM 上后执行,不会阻塞DOM 的更新
- useLayoutEffect 会在渲染的内容更新到DOM 上之前执行,会阻塞DOM 的更新
const App = memo(() => {
useLayoutEffect(() => {
console.log("第二步执行")
})
useEffect(() => {
console.log("第四步执行")
})
console.log("第一步执行")
return(
<div>第三步执行</div>
)
})
三、自定义hook
1、对组件中的hooks 的抽取
function useLogLife(comName) {
useEffect(() => {
console.log(comName + "组件被创建")
return () => {
console.log(comName + "组件被销毁")
}
}, [])
}
const Home = memo(() => {
useLogLife("home")
return <h2>home</h2>
})
const About = memo(() => {
useLogLife("about")
return <h2>about</h2>
})
const App = memo(() => {
const [isShow, setIsShow] = useState(true)
useLogLife("app")
return (
<div>
<button onClick={e=>setIsShow(!isShow)}>切换</button>
{ isShow && <Home/> }
{ isShow && <About/> }
</div>
)
})
2、对context 数据进行抽取
// src/context/index.js
import { createContext } from 'react'
const UserContext = createContext()
const TokenContext = createContext()
export {
UserContext,
TokenContext
}
// src/index.js
import { UserContext, TokenContext } from "src/context"
root.render(
<UserContext.Provider value={{name: "why", age: 18}}>
<TokenContext.Provider value={'coder'}>
<App/>
</TokenContext.Provider>
</UserContext.Provider>
)
// src/hooks/index.js
import useUserToken from './useUserToken'
export {
useUserToken
}
// src/hooks/useUserToken.js
import { useContext } from 'react'
import { UserContext, TokenContext } from "@/context"
function useUserToken() {
const user = useContext(UserContext)
const token = useContext(TokenContext)
return [ user, token ]
}
export default useUserToken
// src/App.jsx
import { useUserToken } from "@/hooks"
const Home = memo(() => {
const [user, token] = useUserToken()
return <h2>{user.name}-{token}</h2>
})
3、获取滚动位置
// src/hooks/useScrollPosition.js
import { useState, useEffect } from 'react'
function useScrollPosition() {
const [scrollX, setScrollX] = useState(0)
const [scrollY, setScrollY] = useState(0)
useEffect(() => {
function handleScroll() {
setScrollX(window.scrollX)
setScrollY(window.scrollY)
}
window.addEventListener("scroll", handleScroll)
return () => {
window.removeEventListener("scroll", handleScroll)
}
}, [])
}
// src/App.jsx
import useScrollPosition from '@/hooks/useScrollPosition'
const App = memo(() => {
const [scrollX, scrollY] = useScrollPosition
return <h2>{scrollX}-{scrollY}</h2>
})
4、localStorage 的数据存储
// src/hooks/useLocalStorage.js
import { useEffect, useState } from "react"
function useLocalStorage(key) {
// 1.从localStorage 中获取数据,根据数据创建组件的state
const [data, setData] = useState(() => {
const item = localStorage.getItem(key)
if(!item) return ""
return JSON.parse(item)
})
// 2.监听data 的改变,一旦发生改变就存储最新的data 值
useEffect(() => {
localStorage.setItem(key, JSON.stringify(data))
}, [data])
// 3.将data/setData 的操作返回给组件,让组件可以使用和修改值
return [data, setData]
}
export default useLocalStorage
// App.jsx
const App = memo(() => {
const [abc,setAbc] = useLocalStorage("token")
function setTokenHandle() {
setAbc("james")
}
return (
<div>
<button onClick={setTokenHandle}>设置token</button>
</div>
)
})
四、Redux 中hook
1、使用connect 函数获取redux
npm install @reduxjs/toolkit react-redux
// src/store/index.js
import { configureStore } from "@/reduxjs/toolkit"
import counterReducer from "./modules/counter"
const store = configureStore({
reducer: {
counter: counterReducer
}
})
export default store
// src/store/modules/counter.js
import { createSlice } from "@reduxjs/toolkit"
const counterSlice = createSlice({
name: "counter",
inititalState: {
count: 99
},
reducers: {
counterReducerAction(state, {payload}){
state.count = state.count + payload
}
}
})
export const { counterReducerAction } = counterSlice.action
export default counterSlice.reducer
// src/index.js
import { Provider } from "react-redux"
root.render(
<Provider store={store}>
<App/>
</Provider>
)
// App.jsx
import React, {memo} from 'react'
import {connect} from "react-redux"
const App = memo((props) => {
const {count} = props
function addNumberHandle(num, isAdd = true) {
if(isAdd) {
addNumber(num)
} else {
subNumber(num)
}
}
return (
<div>
<h2>当前计数: { count }</h2>
<button onClick={addNumberHandle(1, true)}>+1</button>
</div>
)
})
const mapStateToProps = (state) => ({
count: state.counter.count
})
const mapDispatchToProps = (dispatch) => ({
addNumber(num) {
dispatch(addNumberAction(num))
}
})
export default connect(mapStateToProps,mapDispatchToProps)(App)
2、在函数式组件中使用useSelector 和 useDispatch
- shallowEqual 的性能优化
- 当useSelector 没有使用shallowEqual 那么组件中使用redux 中的数据时,只要有一个数据发生变化就会重新渲染,
- shallowEqual 的作用,对组件中使用到的redux 中的数据变化前后进行比较,来判断是否需要重新渲染组件,当redux 中state中的count 发生变化,就去重新渲染用到count 的组件
npm install @reduxjs/toolkit react-redux
// src/store/index.js
import { configureStore } from "@/reduxjs/toolkit"
import counterReducer from "./modules/counter"
const store = configureStore({
reducer: {
counter: counterReducer
}
})
export default store
// src/store/modules/counter.js
import { createSlice } from "@reduxjs/toolkit"
const counterSlice = createSlice({
name: "counter",
inititalState: {
count: 99
},
reducers: {
counterReducerAction(state, {payload}){
state.count = state.count + payload
}
}
})
export const { counterReducerAction } = counterSlice.action
export default counterSlice.reducer
// src/index.js
import { Provider } from "react-redux"
root.render(
<Provider store={store}>
<App/>
</Provider>
)
// App.jsx
import React, {memo} from 'react'
import {useSelector, useDispatch,shallowEqual} from "react-redux"
const App = memo((props) => {
// 1.使用useSelector 将redux 中store 的数据映射到组件内
const { count } = useSelector((state) => ({
count: state.counter.count
}), shallowEqual)
// 2.使用useDispatch 直接派发action
const dispatch = useDispatch()
function addNumberHandle(num, isAdd = true) {
if(isAdd) {
dispatch(addNumberDispatch(num))
} else {
dispatch(subNumberDispatch(num))
}
}
return (
<div>
<h2>当前计数: { count }</h2>
<button onClick={addNumberHandle(1, true)}>+1</button>
</div>
)
})
export default App
五、react 新增的hook
1、服务器端渲染(SSR)
a、SPA:单页面富应用 存在两个问题
- 首屏的渲染速度,由于浏览器请求服务器数据时,是先请求index.html ,再通过script 从服务器中下载页面引用的js 文件,再由浏览器执行js 中的代码
- SEO 优化(搜索引擎优化)由于浏览器请求服务器数据时,是先请求index.html ,而index.html 中对于搜索引擎的配置主要在meta 中,当浏览器爬虫在获取index.html 中的meta 配置后,会将该配置信息存入数据库中,当用户在浏览器搜索时,会由于meta 配置信息较少而排名靠后,不利于用户查看到自己的页面
b、什么是SSR
- SSR即服务端渲染,指的是页面在服务器端已经生成了完整的HTML的页面结构,不需要浏览器通过执行js 代码来创建页面结构
- 早期的服务端渲染包括PHP,JSP,ASP 等方式
- 可以通过借助于Node 来执行js 代码,提前完成页面的渲染
c、CSR 客户端渲染
- 开发的SPA 页面通常依赖的就是客户端渲染
d、SSR 同构应用
- 当用户发出请求时,先在服务器通过SSR 渲染出首页的内容
- 但是对应的代码同样可以在客户端被执行
- 执行的目的包括事件绑定等以及其他页面切换时也可以在客户端被渲染
2、useId
- useId 是一个用于生成横跨服务器和客户端的稳定的唯一ID 的同时避免hydration 不匹配的hook
const id = useId()
<label htmlFor={id}>
<input id={id} type="text "/>
</label>
3、useTransition
- 延迟更新
4、useDeferredValue
- useDeferredValue 接受一个值,并返回该值的新副本,该副本将推迟到更紧急的更新之后