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 ThemeContextUserContext
import React from "react";
const UserContext = React.createContext({name: '张三', age: 20});
export default UserContextApp.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 AppuseReducer在实际项目中用的不多,了解即可
主要应用场景:
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 AppuseCallback第二个参数也就是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 AppuseCallback 和 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 AppuseRef保存一个数据,这个对象在整个生命周期中可以保存不变
验证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 AppforwardRef本身做法没有问题,但是我们将子组件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 Appreact18新增了关于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;