React之Redux浅析与实战应用

166 阅读8分钟

1、Redux简介

1.1、Redux是什么

Redux 是 JavaScript 应用的状态容器,提供可预测的状态管理。

Redux使用不依赖任何框架,可以在React里面使用,也可以在Vue或这Angular里面使用

那么我们什么时候需要使用Redux呢?

  • 第一,组件需要共享数据(或者叫做状态state)的时候;
  • 第二,某个状态需要在任何地方都可以被随时访问的时候;
  • 第三,某个组件需要改变另一个组件的状态的时候。

如下图简单概括一下:统一保存数据store,在隔离了数据与UI的同时,负责处理数据的绑定。

image.png

1.2、Redux工作流

一个现实生活中的例子来形象化Redux的工作流程 image.png

首先我们会创建数据仓库store,reducer同时初始化数据state;react组件订阅store,store中的数据就会被推送过来,然后渲染ui;如果某个组件需要更改数据,那么他会发送一个action,这个过程叫做dispatch。action会以事件驱动的方式被store截获,然后store就会把自己的当前的数据以及指令action传递给reducer,由reducer去更新数据;而reducer更新完成以后就会向store输出return一个新的state,store取得新数据以后就会向订阅了自己的react组件推送这个新数据。 image.png

2、Redux实战

npm install redux
// 1-创建数据存储store
import {createStore} from 'redux';
const store = createStore(languageReducer);
// 2-获取store仓库里面的数据;
const [language, setLanguage] = useState(store.getState());
// 3-store.dispatch派发数据
store.dispatch(addLanguageCreator(action));
// 3.1-改变语言的action
export const changeLanguageCreator = (state: string): IAction => {
    return {
        type: CHANGE_LANGUAGE,
        payload: state
    }
}
// 3.2-reducer函数是一个纯函数,return直接返回结果,不经过其它的业务逻辑处理
const languageReducer = (state = defaultLanguage, action: IAction): IDefaultLanguage => {
    switch (action.type) {
        case 'change_language':
            return {...state, value: action.payload}
        case 'add_language':
            return {...state, languageList: [...state.languageList, action.payload]}
        default:
            return state
    }
}
// 4-store.subscribe订阅数据
store.subscribe(() => {
    setLanguage(store.getState());
});

image.png

真实项目中的Redux架构

一般来说,使用redux都会创建一个用来存放数据的store,store中有若干个reducer,然后我们还需要利用react组件来渲染ui,除此以外我们还会有若干个与reducer对应的action指令。

  1. store中的reducer组合在一起就形成了项目的数据仓库,redux称之为state,我们简单理解为数据就可以了。
  2. react组件可以通过订阅store来获得state数据,然后通过数据来渲染UI。
  3. ui会通过屏幕显示给用户,而用户可以通过鼠标和键盘与react组件进行交互,在交互过程中不可避免的会发生数据的改变。
  4. 但是state数据是单向流动的,对react组件来说是只读的,所以UI组件不可以直接访问store修改数据。ui必须通过向store发送action指令来让sotre自己修改自己,而这个指令分发的过程就叫做dispatch。
  5. 当aciton指令到达store以后,可能会先依次经过若干个middleware中间件进行数据预处理,而数据的异步处理也是在这里进行的。
  6. 预处理过后的数据就会连同action指令一起传递给reducer,reducer就会按照action中描述的指令来更新数据state。
  7. 当state更新好以后,就会马上把新的数据推送给订阅了store的组件,而组件就会根据新数据重新渲染UI,用户就能看到变化了。

image.png

3、React-redux与React-thunk(中间件) 实战

react-redux官网:react-redux.js.org/introductio…

// react-redux 8.0以上的版本已经全名支持ts,8.0以下的版本还需要下载npm install @types/react-redux --save-dev来支持ts
npm install redux
npm install react-redux 
// 1-创建数据存储store
import {createStore} from 'redux';
const store = createStore(languageReducer);
// 2-index.tsx根组件 引入数据仓库store,store就可以从全局范围内使用了
import store from './redux/store';
// provider组件,上下文对象context api的数据组件,通过使用provider,就可以从组件中把store的订阅剥离出来,实现真正意义上的组件化
import { Provider } from 'react-redux';
<Provider store={store}>
    <App />
</Provider>
// 3-useSelector接收store里面的数据(接收、订阅)
const language = useSelector((state: IDefaultLanguage) => state);
// 4-dispatch派发数据
const dispatch = useDispatch();
const menuHandleClick = (e: any) => {
    if (e.key === 'new') {
        const action = {
            name: '新语言',
            code: 'new_lang',
        };
        // dispatch派发数据
        dispatch(addLanguageCreator(action));
    } else {
        // dispatch派发数据
        dispatch(changeLanguageCreator(e.key));
    }
};

Redux-thunk应用: 处理异步的数据请求

常规的redux处理数据流程:

image.png

异步处理数据的流程:

image.png

combineReducers:连接store里面的多个数据

npm install redux-thunk
/**
 * ThunkAction的泛型接收四个参数
 * 第一个是当前函数的返回值R,代表return,要求我们定义最终输出类型,不过我们的giveMeDataActionCreator是个返回函数的类型,所以最终输出是void,也就是没有任何数据输出。
 * 第二个参数S比较简单,指的是StoreState,需要输入的是我们store的类型,也就是rootstate,请同学们从store中引入rootstate
 * 第三个参数E,代表extra,意思就是定义一下我们action中额外的参数,不过我们没参数,所以是unknow,或者也可以写undefined。
 * 最后一个A,就是我们的action,我们直接使用混合aciton类型RecommendProductAction就可以了
 */
export const getDataCreator = (): ThunkAction<void, TRootStoreStateTyep, unknown, IDetailInfoActionsCreator> => async (dispatch) => {
    const { data } = await axios.get('http://123.56.149.216:8080/api/productCollections');
    dispatch(detailInfoActionsCreator(data));
};

image.png

4、Redux-toolkit(RTK) 实战

4.1、简介

Redux-toolkit的项目依赖:

  • redux:RTK需要安装redux才能工作,安装了RTK,就不需要重复安装redux了。但是,它的依赖项并不包含react-redux。所以说,redux-toolkit并不只是给react项目使用的,你要是愿意,也可以在vue、或者angular、甚至是jQuery项目中使用。如果想再redux-toolkit项目中使用react-redux,所以得手动安装;
  • 第二个依赖,RTK自带了redux-thunk来处理异步逻辑,thunk在RTK项目中是默认启动的,开发过程中也可以关闭或者是使用redux-sage等其他异步处理中间件来替代thunk;
  • 第三个reselect,这也是一个比较流行的redux插件,他可以帮助我们在使用state selector的时候记住当前的状态,这样就可以防止你的组件在不需要的时候被无意识的渲染了;
  • 最后一个是immer。它允许我们把state从immutable(纯函数,永恒的,不可改变的)转化为mutable(可变的,易变的),我们可以在reducer中直接更改state数据了。

image.png

Redux-toolkit(RTK)官网:redux-toolkit.js.org/api/createR…

建议使用官方提供的脚手架来安装RTK项目,默认配置配置好了,只需要写业务代码就可以。

npx create-react-app my-app --template redux-typescript

项目开发中一般不会直接使用createAction和createReducer这两个函数,取而代之的是使用createSlice。

image.png

4.2、createSlice

createSlice函数接受初始化state和对象化映射后的reducer,可以将store以切片slice的方式分割成为不同的部分,每个部分都会独立、而且自动生成相对应的action与state对象。

我们直接来看一下代码示例:

  • createSlice函数的参数是个对象,
  • 第一个字段是slice名称,相当于分割store以后的命名空间。
  • 第二个字段是initialState,就是数据初始化。因为initialState是redux数据启动的必要前提,而在传统的reducer中,我们必须要在参数上使用等号来定义initialState,有时候程序员会忘记写上initialState,这时候程序就会报错。现在,在RTK中我们直接使用在对象中强制定义了initialState,这样就可以完美避免state数据悬空的问题。
  • 第三个字段是reducer,但是这是一个特殊的reducer,因为他把action和reducer结合在一起了,这与我们刚刚介绍过的createReducer是一样的。这里immer在最底层帮我们重建state对象。

image.png

4.3、configStore

configStore会带有一些预先定义好的参数,而参数也是以对象的形式传递进来的。

  • reducer:就不用多说了吧,定义了store的state数据的初始化与变化过程
  • Middleware:中间件,Middleware,值得说道一下。这里会默认传入两个中间件,一个是react-thunk,另一个就是immer,我们可以通过getDefaultMiddleware函数取得默认中间件。
  • devTools:开启以后,我们每次dispatch action,都会在console中打印action和state的数据,我们会在接下来代码中尝试一下这个选项。
  • preloadedState: preloadedState,enhancers 与原生reudx 的createstore用法一样,preloadedState用来设置初始化state,而且可以覆盖reducer中的initialState
  • Enhancers:就是提供另一个途径来添加中间件。最后这两个字段用处比较少,可以不需要太关注。

4.4、实战

// 1、使用redux来创建项目,redux相关的配置都是写好的
npx create-react-app my-app --template redux-typescript
// 2、创建store仓库
const rootReducer = combineReducers({
  language: languageSlice.reducer,
  detailInfo: detailInfoSlice.reducer
});
export const store = configureStore({
  reducer: rootReducer
});
// 3、index.tsx引入store,全局可以使用store
<Provider store={store}>
  <App />
</Provider>
// 4、创建组件的slice,createSlice组合了之前的reducer和action
export const languageSlice = createSlice({
    name: 'languageSlice',
    initialState,
    // 可以把reducers看成是reducer和action的结合体
    reducers: {
        changeLanguage(state, action) {
            state.value = action.payload;
        },
        addLanguageCreator(state, action) {
            state.languageList.push(action.payload);
        }
    }
})
// 5、组件调用,useSelector接收store里面的数据,state就是数据仓库里面的值
const language = useAppSelector(state => state.language);
const dispatch = useAppDispatch();
const menuHandleClick = (e: any) => {
    if (e.key === 'new') {
        const action = {
            name: '新语言',
            code: 'new_lang',
        };
        // dispatch派发数据
        dispatch(languageSlice.actions.addLanguageCreator(action));
    } else {
        // dispatch派发数据
        dispatch(languageSlice.actions.changeLanguage(e.key));
    }
};

5、redux-persist 持久化存储

github.com/rt2zz/redux…

npm install redux-persist
// 1、store.ts
import { persistStore, persistReducer } from 'redux-persist';
import storage from "redux-persist/lib/storage";
const persistConfig = {
    key: 'root',
    storage,
    whitelist: ['user']     // whitelist:只有user会被persist
    // blacklist: ['user']  // blacklist:user不会被persist
};
const rootReducer = combineReducers({
    user: userSlice.reducer
});
const persistedReducer = persistReducer(persistConfig, rootReducer);
const store = configureStore({
    reducer: persistedReducer,
    devTools: true
});
const persistor = persistStore(store);
export default {store, persistor};
// 2、入口 index.tsx
import rootStore from './redux/store'
import {PersistGate} from 'redux-persist/integration/react'
<Provider store={rootStore.store}>
    <PersistGate persistor={rootStore.persistor}>
        <App />
    </PersistGate>  
</Provider>
// 3、组件使用
const jwt = useSelector(s => s.user.token);