一篇文章让你理解redux与react-redux的作用与区别!!!

388 阅读9分钟

前言

最近突然从俺哥那里了解到zustand,顺便做了一些英文转中文的md文档,一边翻译的时候也很好奇,目前主流的全局状态管理工具就是redux,所以也就想搞清楚zustandredux比,它的优势到底在于哪里呢? 但是突然转念一下,现在对于reduxreact-redux也已经比较陌生了,于是又催促自己先从初级的开始复习!

一 从redux开始


开始手把手的创建新项目以及引入redux,陆陆续续明白了一些,redux作为react最主流的全局状态管理工具而言,本身是分为三大块的,store reducer action.

  1. 通过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
  1. 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
  1. 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 actionreducer 之间,进行多一层的处理.通常使用的中间件有redux-logger redux-thunk等.

  1. redux-logger 主要主要是用来监控redux的数据变化,并且打印在控制台中,它会输出之前的state以及变化后的state日志记录

image.png

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 StateActions (这里我使用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函数,如果不想触发订阅可以设置为nullundefined.

当设置为函数时,函数的返回值必须是一个对象,并且返回的对象会被合并到被包装的组件的props中,即我们在组件的pops中能够拿到数据内容取决于我们在mapStateToProps中返回了什么内容!函数声明的时候会传入两个参数 stateownProps,解释如下.

    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函数声明的时候会传入两个参数 dispatchownProps,解释如下.

    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!总得自己学习理解了,才能总结说给大家听听我的拙见呢!