Search K
Appearance
Appearance
useState
体验
import {memo, useState} from "react";
const App = memo(() => {
const [message, setMessage] = useState("helloo world")
function handleMessage() {
setMessage("你好张三")
}
return (
<div>
<h2>App</h2>
<div>message: { message } </div>
<button onClick={ () => { handleMessage() } }>改变</button>
</div>
)
})
export default App
假如我们现在有一个需求:浏览器页面标签的title
总是显示counter
的数字,分别使用类组件和函数式组件来实现这个需求:
类组件实现:
import React, {PureComponent} from 'react';
class App2 extends PureComponent {
constructor() {
super();
this.state = {
counter: 100
}
}
componentDidMount() {
document.title = this.state.counter
}
componentDidUpdate(prevProps, prevState, snapshot) {
document.title = this.state.counter
}
changeCounter = (number) => {
this.setState({
counter: number + this.state.counter
})
}
render() {
return (
<div>
<button onClick={() => {
this.changeCounter(-1)
}}>-1
</button>
<button onClick={() => {
this.changeCounter(1)
}}>+1
</button>
</div>
);
}
}
export default App2;
userEffect
名字由来,函数式组件本身也是函数,react
推荐我们编写纯函数,像在这个函数中做一些发送网络请求、操作document
对象、手动更新dom
、一些事件的监听,这些业务本身不该放到纯函数中,属于函数的副作用,react
为此专门提供了一个hook
来管理这些副作用
useEffect
的作用:
1、告诉React
需要在初次渲染后执行某些操作,useEffect
要求我们传入一个回调函数,在React
执行完更新DOM操作之后,就会回调这个函数;
2、useEffect
第二个参数是个数组,如果第二个参数不传,那么当每次组件重新渲染的时候都会重新执行回调函数的逻辑。如果数组中传递了具体的变量参数,只有当数组中的变量值改变的才会重新执行useEffect
中的第一个回调函数逻辑。
函数式组件结合useEffect
来实现
import {memo, useEffect, useState} from "react";
const App = memo(() => {
const [counter, setCounter] = useState(100)
// 告诉react当前组件初次渲染时以后需要执行的副作用代码
useEffect(() => {
console.log("触发useEffect")
document.title = counter
});
const changeCounter = (number) => {
setCounter(counter + number)
}
return (
<div>
<button onClick={() => {
changeCounter(1)
}}>+1
</button>
<button onClick={() => {
changeCounter(-1)
}}>-1
</button>
</div>
)
})
export default App
在class
组件的编写过程中,某些副作用的代码,我们需要在componentWillUnmount
中进行清除,例如清除定时器、事件监听等,利用useEffect
也可以做到,useEffect
返回值可以是一个函数,可以在这里清除副作用
示例:
import {memo, useEffect, useState} from "react";
const App = memo(() => {
const [counter, setCounter] = useState(100)
useEffect(() => {
console.log("进行事件监听")
// 返回一个回调函数,当组件被重新渲染或者组件卸载的时候执行
return () => {
console.log("执行重新渲染或卸载了,取消事件监听")
}
})
const changeCounter = (number) => {
setCounter(counter + number)
}
return (
<div>
<div>
counter: {counter}
</div>
<button onClick={() => {
changeCounter(1)
}}>+1
</button>
</div>
)
})
export default App
初次渲染打印结果:
进行事件监听
初次渲染完成后,我们点击按钮更新了counter
打印结果:
进行事件监听
执行重新渲染或卸载了,取消事件监听
进行事件监听
也就是说,当组件被重新渲染或者组件卸载的时候,会先执行useEffect
返回的回调函数中的逻辑,清除effect
,然后才执行useEffect
中的代码逻辑,这就避免了初始化或者更新的时候设置多个副作用的问题
import {memo, useEffect, useState} from "react";
const App = memo(() => {
useEffect(() => {
console.log("修改title")
})
useEffect(() => {
console.log("进行事件监听")
// 返回一个回调函数,当组件被重新渲染或者组件卸载的时候执行
return () => {
console.log("执行重新渲染或卸载了,取消事件监听")
}
})
useEffect(() => {
console.log("监听redux数据变化")
// 返回一个回调函数,当组件被重新渲染或者组件卸载的时候执行
return () => {
console.log("取消redux监听")
}
})
return (
<div>
</div>
)
})
export default App
前面的示例只要触发了组件的render
那么三个useEffect
都会重新被执行,明显会有性能问题
示例代码:
import {memo, useEffect, useState} from "react";
const App = memo(() => {
const [counter, setCounter] = useState(100)
useEffect(() => {
console.log("监听redux数据")
});
useEffect(() => {
console.log("监听eventBus数据")
});
useEffect(() => {
console.log("触发counteruseEffect")
document.title = counter
});
const changeCounter = (number) => {
setCounter(counter + number)
}
return (
<div>
<button onClick={() => {
changeCounter(1)
}}>+1
</button>
<button onClick={() => {
changeCounter(-1)
}}>-1
</button>
</div>
)
})
export default App
可以利用useEffect
进行优化
优化后的代码每个useEffect
只有当依赖的值变化后才重新执行,传入空数组只会在首次初始化执行一次渲染
import {memo, useEffect, useState} from "react";
const App = memo(() => {
const [counter, setCounter] = useState(100)
// 如果只需要组件渲染初次完成后执行一次,类似mounted
// 可以传递一个空数组,不传则组件每次渲染都会重新执行里面的逻辑
// useEffect(() => {
// console.log("触发useEffect")
// document.title = counter
// }, []);
useEffect(() => {
console.log("监听redux数据")
}, []);
useEffect(() => {
console.log("监听eventBus数据")
}, []);
useEffect(() => {
console.log("触发counteruseEffect")
document.title = counter
}, [counter]);
const changeCounter = (number) => {
setCounter(counter + number)
}
return (
<div>
<button onClick={() => {
changeCounter(1)
}}>+1
</button>
<button onClick={() => {
changeCounter(-1)
}}>-1
</button>
</div>
)
})
export default App
代码示例: ThemeContext
import React from "react";
const ThemeContext = React.createContext({color: 'red', type: 'color'});
export default ThemeContext
UserContext
import React from "react";
const UserContext = React.createContext({name: '张三', age: 20});
export default UserContext
App.jsx
import {memo, useContext} from "react";
import ThemeContext from "../context/ThemeContext";
import UserContext from "../context/UserContext";
const App = memo(() => {
const themeContext = useContext(ThemeContext);
const userContext = useContext(UserContext);
console.log("themeContext",themeContext)
console.log("userContext",userContext)
return (
<div>
context
</div>
)
})
export default App
useReducer
在实际项目中用的不多,了解即可
主要应用场景:
1、state
的处理逻辑比较复杂,使用useReducer
进行拆分
2、这次修改的state
需要依赖之前的state
时,也可以使用
通常使用useCallback
的目的是优化子组件进行多次渲染的问题
例如下面的例子,我们向子组件Child
中传递了一个函数increment
,用于修改父组件中的count
,父组件中还有一个变量message
,这时候我们发现修改setMessage
后触发了父组件的重新渲染,父组件重新定义了increment
函数,导致子组件也重新渲染了,这样当子组件很多的时候会存在性能问题,我们想达到的效果应该是修改message
不会影响子组件重新渲染,因为子组件中没有用到message
利用useCallback
优化子组件多次渲染的问题示例:
子组件,子组件中调用父组件传入的increment
方法
import {memo} from "react";
const Child = memo((props) => {
console.log("Child is render")
return (
<div>
<button onClick={()=>{props.increment()}}>子组件increment</button>
</div>
)
})
export default Child
父组件代码如下
import {memo, useCallback, useState} from "react";
import Child from "./Child,jsx";
const App = memo(() => {
const [count, setCount] = useState(100)
const [message, setMessage] = useState("hello world")
const increment = () => {
setCount(count + 1)
}
console.log("App is render")
return (
<div>
<button onClick={() => {
increment()
}}>+1
</button>
<div>message:{ message }</div>
<button onClick={e=>{ setMessage(Math.random) }}>改变message</button>
<Child increment={increment}></Child>
</div>
)
})
export default App
当点击按钮 子组件increment
时,会发现控制台打印了Child is render
,也就是说每次点击都重新渲染了Child
子组件,原因是因为执行了setMessage
方法,触发App
组件重新渲染,重新定义了一个increment
函数,子组件Child
发现props
中的increment
变化了,所以重新执行了渲染。我们改变的是message
变量,正常来说子组件没用到message
,不应该重新触发渲染,当页面子组件很多的时候可能会出现性能问题。
利用useCallback
进行性能优化:
修改父组件代码
import {memo, useCallback, useState} from "react";
import Child from "./Child,jsx";
const App = memo(() => {
const [count, setCount] = useState(100)
const [message, setMessage] = useState("hello world")
const increment = useCallback(() => {
setCount(count + 1)
}, [count])
console.log("App is render")
return (
<div>
<button onClick={() => {
increment()
}}>+1
</button>
<div>message:{ message }</div>
<button onClick={e=>{ setMessage(Math.random) }}>改变message</button>
<Child increment={increment}></Child>
</div>
)
})
export default App
useCallback
第二个参数也就是count
在值没有改变的情况下,多次定义increment
的时候,返回的值是相同,也就是说每次点击 改变message
虽然执行了setMessage
方法并重新出发了App
的渲染,但是increment
因为使用了useCallback
的原因,返回的increment
函数并没有发生改变(实际每次执行App
的渲染方法,都会重新定义increment
函数,但是useCallback
检测到依赖变量值没有改变,就返回了原先记住的函数)。此时increment
函数没有改变,子组件props
没有改变,这样就不会再触发子组件Child
的重新渲染
前面的代码当count
改变的时候依然会定义新的increment
函数,而导致子组件重新渲染,其实依然有办法使得count
改变的时候依然使用的是同一个函数,避免子组件渲染
前面的示例代码如果useCallback
依赖值是一个空数组,那么即使count
发生变化,increment
用的函数也依然是同一个值,这样虽然可以避免子组件重复渲染,但是会产生闭包陷阱,当一直点击 +1
按钮时发现 count
只增加了一次 ,一直都是 101
,这是因为useCallback
用法错误,导致useCallback
函数内部形成闭包,词法环境中的count
只增加了一次,始终都是101
import {memo, useCallback, useState} from "react";
import Child from "./Child,jsx";
const App = memo(() => {
const [count, setCount] = useState(100)
const [message, setMessage] = useState("hello world")
const increment = useCallback(() => {
setCount(count + 1)
}, [])
console.log("App is render")
return (
<div>
<div>count:{count}</div>
<button onClick={() => {
increment()
}}>+1
</button>
<div>message:{ message }</div>
<button onClick={e=>{ setMessage(Math.random) }}>改变message</button>
<Child increment={increment}></Child>
</div>
)
})
export default App
一些js
中常见的闭包陷阱 常见的闭包陷阱
useCallback
使用不当可能造成闭包陷阱
闭包陷阱
function foo(name){
function bar(){
console.log(name)
}
return bar
}
const bar1 = foo("why")
bar1() // why
bar1() // why
// 这里foo函数传入了新的参数,但是bar1依然是why,形成了闭包陷阱
const bar2 = foo("kobe")
bar2() // kobe
bar1() // why
正确的代码应该如下,但是这样当count
改变的时候依然会触发子组件重新渲染
import {memo, useCallback, useState} from "react";
import Child from "./Child,jsx";
const App = memo(() => {
const [count, setCount] = useState(100)
const [message, setMessage] = useState("hello world")
const increment = useCallback(() => {
setCount(count + 1)
}, [count])
console.log("App is render")
return (
<div>
<div>count:{count}</div>
<button onClick={() => {
increment()
}}>+1
</button>
<div>message:{ message }</div>
<button onClick={e=>{ setMessage(Math.random) }}>改变message</button>
<Child increment={increment}></Child>
</div>
)
})
export default App
可以利用useRef
结合useCallback
进行优化,可以解决闭包陷阱,useRef
,在组件多次渲染的时候,返回的是同一个值,通常useRef
可以解决大部分闭包陷阱
import {memo, useCallback, useRef, useState} from "react";
import Child from "./Child,jsx";
const App = memo(() => {
const [count, setCount] = useState(100)
const [message, setMessage] = useState("hello world")
const countRef = useRef();
countRef.current = count;
// 进一步优化:当count发生改变的时候,也使用同一个函数
// 做法一:将count依赖移除掉,但是会发生闭包陷阱,导致count无法更新
// 做法二:useRef,在组件多次渲染的时候,返回的是同一个值
const increment = useCallback(() => {
setCount(countRef.current + 1)
}, [])
console.log("App is render")
return (
<div>
<div>count:{count}</div>
<button onClick={() => {
increment()
}}>+1
</button>
<div>message:{ message }</div>
<button onClick={e=>{ setMessage(Math.random) }}>改变message</button>
<Child increment={increment}></Child>
</div>
)
})
export default App
在父组件传递给子组件一个函数时,最好使用useCallback
进行一个包裹,这样可以避免子组件多次渲染的问题
useCallback
缓存(记忆)的是函数,而useMemo
缓存的则是函数的返回值,类似vue
中的计算属性
以下示例每次我们点击count++
按钮时,count
改变,都会触发函数重新渲染,然后重新执行totalNum(100)
,存在性能上的浪费,可使用useMemo
进行优化
代码示例:
import {memo, useMemo, useState} from "react";
function totalNum(num){
console.log("totalNum 计算总和")
let total = 0
for(let i = 1; i <= num; i++){
total += i
}
return total
}
const App = memo(() => {
const [count, setCount] = useState(10)
let result = totalNum(100)
return (
<div>
<div>计算结果:{result}</div>
<div>count:{count}</div>
<button onClick={e=>{setCount(count+1)}}>count++</button>
</div>
)
})
export default App
使用useMemo
进行优化,优化后totalNum
函数只会执行一次,点击count++
也只会执行一次,因为result
值没有发生改变,这里useMemo
第一个参数是回调函数,返回的是一个函数执行结果,第二个参数是依赖项,在依赖值没有改变的时候,回调函数咋不会重新执行,如果没有依赖项可以传入空数组
import {memo, useMemo, useState} from "react";
function totalNum(num){
console.log("totalNum 计算总和")
let total = 0
for(let i = 1; i <= num; i++){
total += i
}
return total
}
const App = memo(() => {
const [count, setCount] = useState(10)
let result = useMemo(() => {
return totalNum(100)
},[])
return (
<div>
<div>计算结果:{result}</div>
<div>count:{count}</div>
<button onClick={e=>{setCount(count+1)}}>count++</button>
</div>
)
})
export default App
如果result
结果依赖于count
,那么可以添加count
作为依赖项,这样count
值发生改变就会重新执行totalNum
函数,如果没有改变则不会重新执行
import {memo, useMemo, useState} from "react";
function totalNum(num){
console.log("totalNum 计算总和")
let total = 0
for(let i = 1; i <= num; i++){
total += i
}
return total
}
const App = memo(() => {
const [count, setCount] = useState(10)
let result = useMemo(() => {
return totalNum(count)
},[count])
return (
<div>
<div>计算结果:{result}</div>
<div>count:{count}</div>
<button onClick={e=>{setCount(count+1)}}>count++</button>
</div>
)
})
export default App
useCallback
和 useMemo
对比:
const increment = useCallback(fn,[])
// 等同于
const increment = useMemo(() => fn,[])
假设我们有一个对象传递到子组件Child
中,这时候点击count++
那么会执行setCount
,然后重新渲染App
,那么子组件中也会重新执行渲染函数
import {memo, useMemo, useState} from "react";
const App = memo(() => {
const [count, setCount] = useState(10)
const info = {name: "张三",age:20}
return (
<div>
<button onClick={e=>{setCount(count+1)}}>count++</button>
<Child info={info}/>
</div>
)
})
export default App
可以使用useMemo
进行优化,此时依赖没有改变,那么每次执行setCount
就不会重新渲染
对子组件传递相同内容的对象时,使用useMemo进行性能的优化
import {memo, useMemo, useState} from "react";
const App = memo(() => {
const [count, setCount] = useState(10)
const info = useMemo(() => ({name: "张三",age:20}),[]
return (
<div>
<button onClick={e=>{setCount(count+1)}}>count++</button>
<Child info={info}/>
</div>
)
})
export default App
import {memo, useRef, useState} from "react";
const App = memo(() => {
const inputRef = useRef();
const getInputDom = () => {
console.log(inputRef.current);
inputRef.current.style.color = 'red'
inputRef.current.value = '张三';
inputRef.current.focus()
}
return (
<div>
<input type="text" ref={inputRef}/>
<button onClick={ () => { getInputDom() } }>获取ref</button>
</div>
)
})
export default App
useRef
保存一个数据,这个对象在整个生命周期中可以保存不变
验证useRef
的不变性,以下示例第一次输出false
,后续点击都输出true
import {memo, useRef, useState} from "react";
let obj = null
const App = memo(() => {
const [count, setCount] = useState(10)
const nameRef = useRef()
console.log(obj == nameRef)
obj= nameRef
return (
<div>
<button onClick={e=>{setCount(count+1)}}>count++</button>
</div>
)
})
export default App
利用useRef
特性也可以解决闭包陷阱
import {memo, useCallback, useRef, useState} from "react";
const App = memo(() => {
const [count, setCount] = useState(10)
// 通过ref解决闭包陷阱
const countRef = useRef()
countRef.current = count
const increment = useCallback(() => {
console.log("执行increment")
setCount(countRef.current + 1)
},[])
return (
<div>
<h2>count: { count }</h2>
<button onClick={e=>{increment()}}>count++</button>
</div>
)
})
export default App
回顾之前的forwardRef
用法
import {forwardRef, memo, useRef} from "react";
const Input = memo(forwardRef((props, ref) => {
return (
<input type="text" ref={ref}/>
)
}))
const App = memo(() => {
const inputRef = useRef()
function handleDOM() {
inputRef.current.focus()
inputRef.current.value = "张三"
}
return (
<div>
<Input ref={inputRef}/>
<button onClick={() => { handleDOM() }}>focus</button>
</div>
)
})
export default App
forwardRef
本身做法没有问题,但是我们将子组件DOM
直接暴露给了父组件,这样父组件可以拿到DOM
做任意操作,会导致情况不可控,开发中显然不建议这样做
使用useImperativeHandle
我们可以给父组件的ref
暴露指定的操作,例如只允许focus
,不允许直接修改输入框的值,要使用子组件提供的setValue
才可以正常修改,应该这样修改
import {forwardRef, memo, useImperativeHandle, useRef} from "react";
const Input = memo(forwardRef((props, ref) => {
// 重新定义一个内部的ref,避免直接绑定父组件传入进来的ref
const inputRef = useRef()
useImperativeHandle(ref,() => {
return {
focus(){
inputRef.current.focus()
},
setValue(value){
inputRef.current.value = value
}
}
})
return (
<input type="text" ref={inputRef}/>
)
}))
const App = memo(() => {
const inputRef = useRef()
function handleDOM() {
inputRef.current.focus()
inputRef.current.setValue("张三")
}
return (
<div>
<Input ref={inputRef}/>
<button onClick={() => { handleDOM() }}>focus</button>
</div>
)
})
export default App
useLayoutEffect
和useEffect
的区别就在于执行完组件的render
函数后,即将将DOM
打印(呈现)到屏幕上,useLayoutEffect
会在呈现在屏幕上之前执行,在这里可以进行一些数据更新调整,useEffect
会在呈现在屏幕之后执行
App.jsx
import {memo, useEffect, useLayoutEffect, useState} from "react";
const App = memo(() => {
const [count, setCount] = useState(10)
useEffect(() => {
console.log("useEffect")
}, []);
useLayoutEffect(() => {
console.log("useLayoutEffect")
}, []);
console.log("render")
return (
<div>
<div>count:{count}</div>
<button onClick={e=>{setCount(count+1)}}>count++</button>
</div>
)
})
export default App
执行顺序:
render
useLayoutEffect
useEffect
一个简单示例,页面上有一个更新的操作将count
设置为0
,然后在useEffect
中监听,如果设置为0
则重新赋值,会发现这里用useEffect
赋值的时候会出现屏幕闪烁的情况,可以发现count
的值先渲染成为了0
之后才重新渲染的
import {memo, useEffect, useLayoutEffect, useState} from "react";
const App = memo(() => {
const [count, setCount] = useState(10)
// 默认情况下不设置依赖项,初始化以及数据更新都会重新调用
useEffect(() => {
console.log("useEffect")
if (count === 0) {
setCount(Math.random() + 99)
}
});
console.log("render")
return (
<div>
<div>count:{count}</div>
<button onClick={e => {
setCount(0)
}}>count++
</button>
</div>
)
})
export default App
如果使用useLayoutEffect
则不会出现这种情况
import {memo, useEffect, useLayoutEffect, useState} from "react";
const App = memo(() => {
const [count, setCount] = useState(10)
// 默认情况下不设置依赖项,初始化以及数据更新都会重新调用
useLayoutEffect(() => {
console.log("useLayoutEffect")
if (count === 0) {
setCount(Math.random() + 99)
}
});
console.log("render")
return (
<div>
<div>count:{count}</div>
<button onClick={e => {
setCount(0)
}}>count++
</button>
</div>
)
})
export default App
注意:自定义hook
必须以use
开头,例如useXXX
需求:所有的组件在创建和销毁时都进行打印
import {memo, useEffect, useState} from "react";
function useComponentLog(name){
useEffect(() => {
console.log(`${name}组件创建`)
return ()=>{
console.log(`${name}组件销毁`)
}
},[])
}
const Home = memo(() => {
useComponentLog("home")
return (
<h2>home</h2>
)
})
const Profile = memo(() => {
useComponentLog("profile")
return (
<h2>profile</h2>
)
})
const App = memo(() => {
const [showComponent, setShowComponent] = useState(true)
return (
<div>
<button onClick={e => {
setShowComponent(!showComponent)
}}>切换显示
</button>
{ showComponent && <Home/> }
{ showComponent && <Profile/> }
</div>
)
})
export default App
需求:共享token
以及用户信息
index.jsx
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<TokenContext.Provider value={"asasssdzxzxassass"}>
<UserContext.Provider value={{name: '李四', age: 30}}>
<App/>
</UserContext.Provider>
</TokenContext.Provider>
);
自定义hook
import {memo, useContext, useEffect, useState} from "react";
import userContext from "./context/UserContext";
import tokenContext from "./context/TokenContext";
/**
* 获取token以及user信息
*/
function useUserAndToken() {
const user = useContext(userContext)
const token = useContext(tokenContext)
return [user, token]
}
const Home = memo(() => {
const [user, token] = useUserAndToken()
return (
<div>
<h2>home</h2>
<div>token: {token}</div>
<div>user: {JSON.stringify(user)}</div>
</div>
)
})
const Profile = memo(() => {
const [user, token] = useUserAndToken()
return (
<div>
<h2>Profile</h2>
<div>token: {token}</div>
<div>user: {JSON.stringify(user)}</div>
</div>
)
})
const App = memo(() => {
return (
<div>
<Home/>
<Profile/>
</div>
)
})
export default App
需求:获取当前页面的滚动位置
import {memo, useEffect, useState} from "react";
/**
* 获取页面滚动位置
*/
function useScrollPosition() {
const [scrollPosition, setScrollPosition] = useState(0)
const handleScroll = () => {
setScrollPosition(window.scrollY)
}
useEffect(() => {
document.addEventListener("scroll", handleScroll)
}, []);
return scrollPosition
}
const Home = memo(() => {
const position = useScrollPosition()
return (
<div>
<h2>home</h2>
<h2>position: {position}</h2>
</div>
)
})
const Profile = memo(() => {
const position = useScrollPosition()
return (
<div>
<h2>Profile</h2>
<h2>position: {position}</h2>
</div>
)
})
const App = memo(() => {
return (
<div style={{overflowY: 'auto'}}>
<div style={{height: '2000px'}}>
<Home/>
<Profile/>
</div>
</div>
)
})
export default App
需求,封装一个storage
存储工具函数,可以设置以及获取storage
中的值
import {memo, useEffect, useState} from "react";
/**
* 抛出两个返回结果,根据key获取到的值,设置值函数
*/
function useLocalStorage(key) {
const [data, setData] = useState(() => {
const item = localStorage.getItem(key)
if(!item) return ""
return JSON.parse(item);
})
useEffect(() => {
console.log("执行useEffect")
localStorage.setItem(key, JSON.stringify(data))
}, [data]);
return [data, setData]
}
const Home = memo(() => {
const [token, setToken] = useLocalStorage("token")
return (
<div>
<h2>home</h2>
<h2>token: {token}</h2>
<button onClick={() => {
setToken("测试token")
}}>重新设置值</button>
</div>
)
})
const App = memo(() => {
return (
<div>
<Home/>
</div>
)
})
export default App
react18
新增了关于redux
的hooks
,之前我们组件中要使用redux
需要使用connect
函数,其原理是将state
和dispatch
传入到props
中,之前的使用方式较为繁琐,需要定义mapStateToProps
和mapDispatchToProps
使用connect
函数使用redux
import React, {memo, PureComponent} from 'react';
import {connect} from "react-redux";
import {addNumber} from "./store/modules/counter";
const App = memo((props) => {
return (
<div>
<h2>
Home counter: {props.count}
</h2>
<div>
<button onClick={() => {
props.addNumber(1)
}}>+1
</button>
<button onClick={() => {
props.addNumber(5)
}}>+5
</button>
</div>
</div>
)
})
const mapStateToProps = (state) => ({
count: state.counter.count
})
const mapDispatchToProps = (dispatch) => ({
addNumber: (count) => dispatch(addNumber(count))
})
export default connect(mapStateToProps, mapDispatchToProps)(App);
接下来使用hooks
进行改造
import React, {memo, PureComponent} from 'react';
import {useDispatch, useSelector} from "react-redux";
import {addNumber} from "./store/modules/counter";
const App = memo((props) => {
const { count } = useSelector((state) => ({
count: state.counter.count
}));
const dispatch = useDispatch();
function incrementCount(num){
dispatch(addNumber(num))
}
return (
<div>
<h2>
Home counter: {count}
</h2>
<div>
<button onClick={() => {
incrementCount(1)
}}>+1
</button>
<button onClick={() => {
incrementCount(5)
}}>+5
</button>
</div>
</div>
)
})
export default App;
在下面的例子中,我们点击+1
按钮调用incrementCount
函数,触发App
组件重新渲染,同时我们发现子组件Home
也重新渲染了,这也会引起页面性能问题,之所以会渲染是因为 useSelector
机制是state
中的任意一个数据变化都会重新引起组件的渲染 。
import React, {memo, PureComponent} from 'react';
import {useDispatch, useSelector} from "react-redux";
import {addNumber} from "./store/modules/counter";
const Home = memo(() => {
const { message } = useSelector((state) => ({
message: state.counter.message
}));
console.log("Home render");
return (
<div>
<h2>home</h2>
<div>message: { message }</div>
</div>
)
})
const App = memo((props) => {
const { count } = useSelector((state) => ({
count: state.counter.count
}));
const dispatch = useDispatch();
function incrementCount(num){
dispatch(addNumber(num))
}
console.log("App render")
return (
<div>
<h2>
Home counter: {count}
</h2>
<div>
<button onClick={() => {
incrementCount(1)
}}>+1
</button>
<button onClick={() => {
incrementCount(5)
}}>+5
</button>
</div>
<div>
<Home/>
</div>
</div>
)
})
export default App;
进行优化,useSelector
可以接收一个函数,用于比较两个值,可以用react-redux
提供的shallowEqual
(前面SCU
有讲到,是做了一个浅层比较),优化或如果Home
组件中使用的state
值没有变化那么久不会再引起Home
组件的重新渲染
import React, {memo, PureComponent} from 'react';
import {shallowEqual, useDispatch, useSelector} from "react-redux";
import {addNumber} from "./store/modules/counter";
const Home = memo(() => {
const { message } = useSelector((state) => ({
message: state.counter.message
}),shallowEqual);
console.log("Home render");
return (
<div>
<h2>home</h2>
<div>message: { message }</div>
</div>
)
})
const App = memo((props) => {
const { count } = useSelector((state) => ({
count: state.counter.count
}));
const dispatch = useDispatch();
function incrementCount(num){
dispatch(addNumber(num))
}
console.log("App render")
return (
<div>
<h2>
Home counter: {count}
</h2>
<div>
<button onClick={() => {
incrementCount(1)
}}>+1
</button>
<button onClick={() => {
incrementCount(5)
}}>+5
</button>
</div>
<div>
<Home/>
</div>
</div>
)
})
export default App;