前言
最近突然从俺哥那里了解到zustand
,顺便做了一些英文转中文的md文档,一边翻译的时候也很好奇,目前主流的全局状态管理工具就是redux
,所以也就想搞清楚zustand
跟redux
比,它的优势到底在于哪里呢?
但是突然转念一下,现在对于redux
和react-redux
也已经比较陌生了,于是又催促自己先从初级的开始复习!
一 从redux开始
开始手把手的创建新项目以及引入redux,陆陆续续明白了一些,redux作为react最主流的全局状态管理工具而言,本身是分为三大块的,store
reducer
action
.
- 通过redux的createStore函数来创建整个store对象,并且默认导出,这样我们就能在各个文件中引入store并且使用store.getState()和store.dispatch()以及store.subscribe()等最基本的函数来实现自己需要的功能.
import {applyMiddleware, createStore} from 'redux'
import reducer from './reducer'
import {createLogger} from 'redux-logger'
import thunk from "redux-thunk";
const logger = createLogger()
const store = createStore(reducer,applyMiddleware(logger,thunk))
export default store
reducer
本身其实就是一个函数,他有两个参数,第一个参数表示之前的state,默认为undefined,第二个参数为action,整个reducer其实可以当成一个数据处理中心来看待,redux所有的数据处理都需要通过action来实现,根据旧数据进行处理,从而返回一个新的数据给store.
const state = {
data:'redux的原始数据'
}
const reducer = function (prevState = state, action) {
switch (action.type) {
case 'SET_PARENT_DATA': {
return {
data:action.data
}
}
case 'SET_CHILDREN_DATA':{
return {
data:action.data
}
}
default:{
return prevState
}
}
}
export default reducer
action
本身是一个对象,顾名思义动作,使用方式是需要通过dispatch函数来进行派发动作,reducer通过action.type属性来区分具体是哪一种操作,action.data用来传递所携带的数据.
export const SET_PARENT_DATA = data => ({type:'SET_PARENT_DATA',data:data})
export const SET_CHILDREN_DATA = data => ({type:'SET_CHILDREN_DATA',data:data})
基本的使用也已经都会了,接下来开始学习redux的中间件,其实就是为了增强redux的功能所引入的模块,中间件的触发时机是在dispatch action 与 reducer 之间,进行多一层的处理.通常使用的中间件有redux-logger
redux-thunk
等.
- redux-logger 主要主要是用来监控redux的数据变化,并且打印在控制台中,它会输出之前的state以及变化后的state日志记录
2.总是听网上的同志们交流,说redux-thunk是为了实现redux的异步操作所创造的,action本身是一个对象,且是一个同步操作,如果说我们需要异步操作的话,对象肯定是无法支持的,所以只能改成函数的方式,俺试了一下确实可以实现异步的action操作,如果不引入redux-thunk中间件的话,控制台是会报错的,它会提示告诉你action必须是一个对象, 例子如下:
export const SET_ASYNC_DATA = data => {
return (dispatch)=>{
setTimeout(()=>{
dispatch({type:'SET_ASYNC_DATA',data})
},2000)
}
}
但是后续我转念一下,如果说只是单纯的需要获取异步数据,并且进行再对store中的数据进行修改,完全不需要redux-thunk,顿时我就感觉它很鸡肋,因为如果只是单纯的获取异步数据,我完全可以在成功获取数据之后再调用dispatch函数,不也是一样的效果吗?
store.dispatch(SET_CHILDREN_DATA(data))
axios.get(`某个接口`).then(res=>{
store.dispatch(SET_ASYNC_DATA(res.data.data))
})
后面才明白,redux-thunk
的作用并不是说为了支持异步操作,而是为了让action支持函数的形式,函数的方式会导致action会更加灵活,实现任意的效果,比如一个函数内同时需要调用多个动作,并不是说只为了这个效果,最后还是为了更好的拓展性,毕竟函数还是比对象要更加灵活!!!
同时使用redux的过程我发现了一个很严重的问题!
在我使用redux的时候我发现了一个问题
,一开始页面显示store中的默认数据,后面我对store中的数据进行修改,但是页面仍然显示的是旧数据,并且我多次打印确认数据是修改成功了,总的来说问题就是数据发生了变化,页面没有及时更新。
于是我使用了this.setState({})
去手动触发页面更新是没问题的,并且后续了解到subscribe
订阅方法,subscribe
可以监听数据的变化,监听到变化后返回新的页面.
store.subscribe(()=>{
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
})
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
但是我还是觉得这种方法有点low,想找寻一种好的办法来解决,于是我才来研究react-redux,并想确认react-redux是否能够解决我使用
二 研究react-redux中文官方文档
我的疑问: react-redux
是否能够解决redux
无法自动更新数据的问题?
react-redux 是 react 官方的Redux UI绑定库
,意思就是,官方大大出的肯定好!给出的官方解释是:它能帮助你提升性能优化,React 通常都会很快,但默认情况下的,对组件的任何更新都会导致 React 重新渲染组件树该部分内的所有组件。确实需要这样做,如果给定组件的数据没有更改,那么重新渲染可能会浪费一些性能,因为请求到的 UI 输出将是相同的。
如果性能是一个问题,最好的提高性能的最佳方法就是跳过不必要的重新渲染,以便组件仅在其数据实际更改时重新渲染。 React Redux 在内部实现了许多性能优化,以便你编写的组件仅在实际需要时重新渲染。
此外,通过在 React 组件树中连接多个组件,你可以确保每个连接的组件仅从该组件所需的 store state 中提取特定的数据。 这意味着你自己的组件将需要更少的重新渲染,因为大多数时候这些特定的数据都没有改变。
第一步 Provider
react-redux 推出了一个Provider
组件,官方的解释是这使得 Redux store 能够在应用的其他地方使用,使用方法其实就是在App.js文件中引入Provider
包裹App组件,并传入store属性
import React from 'react'
import ReactDOM from 'react-dom/client'
import { Provider } from 'react-redux'
import store from './store'
import App from './App'
// 从 React 18 开始
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<Provider store={store}>
<App />
</Provider>
)
第二步 创建Redux State Slice(官方名词,听不懂,我理解就是创建reducer)
import {createSlice} from '@reduxjs/toolkit'
export const counterSlice = createSlice({
name:"counter",
initialState:{
data:'newReducer的默认数据'
},
reducers:{
setData:(state,action)=>{
console.log(action)
state.data = 'newReducer的修改数据'
}
}
})
export const {setData} = counterSlice.actions
export default counterSlice.reducer
在该文件中,导出生成的Redux action creators
(官方解释听不懂,我理解的就是导出action函数)和整个 slice reducer
函数(我理解就是reducer对象)
第三步 添加 Slice Reducers 到 Store (我理解就是相当于store.js文件,创建store对象,并且引入之前创建好的reducer对象,并且需要通过特定的工具configureStore
才能进行配置)
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';
const store = configureStore({
reducer: {
counter: counterReducer,
},
});
export default store;
第四步 在React组件中使用Redux State
和 Actions
(这里我使用hooks 函数式组件)
import {setData} from '../redux/newReducer'
import {useDispatch} from "react-redux";
export default function Function() {
const dispatch = useDispatch()
return(
<div onClick={()=>{dispatch(setData())}}>
{result}
</div>
)
}
通过最终效果,当我通过点击事件成功的派发了setData
函数,并且成功修改数据,最重要的是,它确实解决了我之前遇到的问题,及时更新了页面!但是整体使用下来,我有一个感受,就是很复杂,需要引入各种模块插件例如createSlice
configureStore
等来进行配置,总体形式跟redux
没有很大区别,都是以action
reducer
store
为核心,于是我开始寻求更加简介方便的方法,希望能够更加简单和方便.
三 寻求更加简洁方便的方法,并且能够解决我的问题!!!
connect
API
前提:也要使用Provider
组件
关于connect
的官方描述是这样的:该函数将React组件连接到Redux存储.它为其连接的组件提供从存储中需要的数据片段,以及可以用于将操作调度到存储的功能.它不会修改传递给它的组件类;相反,它会返回一个新的、连接的组件类,该类包装您传入的组件。
我的理解:connect
函数能够让组件连接到redux,获取redux的数据,也能修改redux中的数据,通过包装组件,并返回一个新组件.
function connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)
第一个参数mapStateToProps
如果设置为函数的话,就相当于组件将订阅redux中store更新,stroe发生变化就会调用mapStateToProps
函数,如果不想触发订阅可以设置为null
或undefined
.
当设置为函数时,函数的返回值必须是一个对象,并且返回的对象会被合并到被包装的组件的props中,即我们在组件的pops中能够拿到数据内容取决于我们在mapStateToProps
中返回了什么内容!函数声明的时候会传入两个参数 state
和 ownProps
,解释如下.
const mapStateToProps = (state, ownProps) => ({
todo: state.todos[ownProps.id],
})
//第一个参数 state 表示redux store 中的 state,我们可以直接拿到redux中的数据
//第二个参数 ownProps 表示 被包装的组件最初的props
总结:mapStatesToProps
的作用就是可以让被包装的组件能够在props中拿到redux store 中的数据
第二个参数mapDispatchToProps
其实概念跟mapStateToProps
差不多,可以设置为对象,函数或者不提供,默认情况下,组件将接收dispatch
,即可以在props中获取到dispatch
函数声明的时候会传入两个参数 dispatch
和 ownProps
,解释如下.
const mapDispatchToProps = (dispatch, ownProps) => ({
toggleTodo: () => dispatch(toggleTodo(ownProps.todoId)),
//第一个参数dispatch 即 redux store 中的 dispatch
//第二个参数 ownProps 表示被包装的组件最初的props
})
总结:mapDispatchToProps
的作用就是可以让被包装的组件能够在props中拿到redux中的dispatch
方法
//用connect包装的组件以及具体的使用方式!
class Connect extends React.Component{
constructor(props){
super(props)
this.state={
}
}
handleClick(){
let {setParentData} = this.props
setParentData('connect数据')
}
render() {
return(
<div onClick={this.handleClick.bind(this)}>
新的Connect组件,用来测试connect是否成功
{store.getState().data}
</div>
)
}
}
export default connect(mapStateToProps,mapDispatchToProps)(Connect)
可以看到,我们使用了connect
包装了整个Connect类组件,并且触发点击事件后,页面也及时的进行了数据更新,所以react-redux
确实解决了redux
所困扰我的问题,它能够自动的监听到store中的数据变化,并且及时的更新页面,我所使用的方法,也是最简单,最原始的方法,也是为了方便大家能够清楚明了的理解!后续打算更新react-redux
有没有用更加高级的办法!!!
redux 与 react-redux 了解的差不多,那么zustand的优势又有哪些呢?
作者去潜心修行去了.........see you next time!总得自己学习理解了,才能总结说给大家听听我的拙见呢!