TypeScript下使用Hooks的方式重新学习Redux和React-Redux
使用过vuex的同学肯定了解vuex无论是集成难度,还是上手难度,都远小于React-Redux,目前要实现vuex同样的功能则需要3个库,分别是Redux、React-Redux、Redux-Saga,它们分别负责下面几种工作。
- Redux:Redux是JavaScript状态容器,提供可预测化的状态管理。
- React-Redux:React官方提供的将React项目与Redux项目绑定的库。
- Redux-Saga:一个用于管理应用程序 Side Effect(副作用,例如异步获取数据,访问浏览器缓存等)的library,它的目标是让副作用管理更容易,执行更高效,测试更简单,在处理故障时更容易。
然而由于Redux-Saga
的复杂性,本篇文章就先暂且不谈,仅仅谈论一下Redux、React-Redux,也正是因为这一系列的库上手成本比较高,于是由蚂蚁金服推出了一个整合库dva
,它整合了这3个库,同时还内置了React-router和fetch,但是从普及程度上面考虑,Redux、React-Redux还是不得不学习一下的。
那么什么时候该使用Redux,我的想法是:当你觉得组件之间的通信让你头痛的时候,你就可以使用Redux。
尤其是在一个界面有非常多的表单需要填写,填写完毕后需要将数据统一起来发送给后端,因为你不可能不将表单分割成为一个一个组件,一旦表单组件被分割,里面的数据传递就会非常复杂。当然还有一种解决方式是不分割组件,全部写在一个组件中(我还真见过这种表单组件,没格式化之前4000行代码,一格式化超过1W行,而且几乎没有注释,你根本不知道有些代码是在干什么,直接无法维护…)。
和Vue一样,在一个比较大的项目中或者一个通信比较复杂的项目中,要实现兄弟组件、爷孙组件的通信是一件非常麻烦和困难的事情(虽然我最近了解到React中有Context),为了解决这个问题,React项目中就引入了Redux
和React-Redux
,值得注意的是,Redux不光可以使用在React项目中,在其它框架的项目中也是可以进行使用。
我们先来尝试一下Redux:
1. 使用Redux
Redux的使用一共分为下面几个步骤:
- 创建state。
- 创建action。
- 通过dispatch进行通信。
当然这是我总结的感觉语言并不官方…下面就来分别看一下这3个步骤吧!
1.1 创建state
因为我特别喜欢TypeScript,所以下面的代码都是使用的TypeScript。
interface action { type: "INCREMENT" | "DECREMENT";}
const counter = (state = 0, action: action) => { switch (action.type) { case "INCREMENT": return state + 1; case "DECREMENT": return state - 1; default: // 不要忘记默认返回值,不然再创建时会报错 return state; }};
1.2 创建Store
const store = createStore(counter);
store.subscribe(() => console.log(store.getState())); // 当store里面的属性改变时,触发的回调
一般来讲是不需要store.subscribe(() => console.log(store.getState()));
这条语句的,但是为了下面我们进行通信的时候能够清楚的看到store中的变化,所以我们添加上这条语句。
1.3 通信
store.dispatch({ type: "DECREMENT" }); // 触发state + 1store.dispatch({ type: "INCREMENT" }); // 触发state - 1
其中可以将{ type: "DECREMENT" }
定义为一个函数。
const increment = (): action => ({ type: "INCREMENT",});
const decrement = (): action => ({ type: "DECREMENT",});
store.dispatch(increment());store.dispatch(decrement());
对于字符串"INCREMENT"
,推荐定义为一个常量,因为毕竟{ type: "INCREMENT" }
中的"INCREMENT"
字符串书写的时候没有提示,非常容易写错,不过用了TypeScript的话是会有提示的,这也是TypeScript比较赞的一点。
如果一旦写错,在TypeScript中还会报错:
所以在JavaScript中因为"INCREMENT"
这一类的字符串书写没有提示的关系,所以很多人会为了防止写错,而给它定义一个常量(当然TypeScript中也推荐这样做)。
const INCREMENT_TYPE = "INCREMENT"; // 定义常量
const increment = (): action => ({ type: INCREMENT_TYPE, // 使用常量进行调用});
store.dispatch(increment());
一旦定义常量后,就会获得代码提示,也就不是那么容易会写错。
1.4 传值
在store.dispatch()
还可以传入一个参数,一般我们定义参数名称为payload
或者data
。
interface action { type: "INCREMENT" | "DECREMENT"; payload?: number; // 传入参数一般命名为 payload 或者 data}
const INCREMENT_TYPE = "INCREMENT";
const increment = (nr: number): action => ({ type: INCREMENT_TYPE, payload: nr,});
const decrement = (): action => ({ type: "DECREMENT",});
const counter = (state = 0, action: action) => { switch (action.type) { case "INCREMENT": return state + (action.payload as number); case "DECREMENT": return state - 1; default: return state; }};
const store = createStore(counter);
store.subscribe(() => console.log(store.getState()));
store.dispatch(increment(5));store.dispatch(decrement());
2. React-Redux
React Hooks的出现让React的易用性升到了另一个等级,我记得我曾经用React的class组件时,需要进行操作的步骤还挺多,而现在React-Redux提供了两个hook,使用这两个hook就可以轻松的访问Store中的状态和触发其中的行为。
由于是项目工程文件,肯定是要进行模块化的,虽然上面的那种写法也是可以,但是会给后期维护带来很大的困难,目前我的习惯是分成:
modules中就放和state相关的东西,比如下面这段代码,就放进modules文件夹下:
const counter = (state = 0, action: action) => { switch (action.type) { case "INCREMENT": return state + (action.payload as number); case "DECREMENT": return state - 1; default: return state; }};
actions就放触发事件相关的代码,比如下面的这些代码就放入actions文件夹下面的文件中:
export interface action { type: "INCREMENT" | "DECREMENT"; payload?: number;}
const INCREMENT_TYPE = "INCREMENT";
export const increment = (nr: number): action => ({ type: INCREMENT_TYPE, payload: nr,});
export const decrement = (): action => ({ type: "DECREMENT",});
而store下的index.ts
文件就将上面所有的文件整合成为一个Store,并且导出,同时还可以添加Redux调试工具,关于Redux调试工具具体信息可以点击查看:
import { combineReducers, createStore } from "redux";import counter from "./modules/counter";import isLogged from "./modules/isLogged";
// 整合const allReducers = combineReducers({ counter, isLogged });
// 注册const store = createStore( allReducers, // @ts-ignore window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() // 引入Redux调试工具);
// 导出export default store;
最后一步就是我们需要引入并且在项目中使用React-Redux。
import { Provider } from "React-Redux";import store from "./store";
ReactDOM.render( <Provider store={store}> <React.StrictMode> <App /> </React.StrictMode> </Provider>, document.getElementById("root"));
上面的Provider
是一个顶层组件,通过它就可以注册一个所有组件都可以访问的store。
到这里为止,我们已经在React项目中注册了store,接下来我们可以随时在需要的时候在组件中访问store中的数据。
需要使用到两个hook,其它的Hook可以参考文章最后的参考链接:
- useSelector:从Redux存储状态中提取数据。
- useDispatch:该hook会返回一个dispatch函数,通过dispatch函数就可以触发actions。
2.1 在组件中调用
知道上面的那两个hook后就可以开始搞事情了。
import React from "react";import "./App.css";import { useDispatch, useSelector } from "react-redux";import { increment } from "./store/actions";
interface AllState { counter: number; isLogged: boolean;}
function App() { // useSelector声明了一个泛型,第一个是state的类型,第二表示返回值的类型 // 即下面的代码counter的类型是number const counter = useSelector<AllState, AllState["counter"]>( (state) => state.counter ); const isLogged = useSelector<AllState, AllState["isLogged"]>( (state) => state.isLogged ); // 调用useDispatch函数会返回一个dispatch函数 const dispatch = useDispatch(); return ( <div className="App"> {counter} {isLogged ? "1" : "2"} <button onClick={() => dispatch(increment(5))}>点我</button> </div> );}
export default App;
因为方便代码演示,所以接口AllState
直接在组件中声明,在一般情况下还是推荐声明在index.ts
中。
2.2 解决type重复
如果你的项目够大,你就会拥有很多的action,那么action type就有可能会出现重复的情况,这就会造成一些BUG的产生,我所知道的解决的方法有两种:
一种是引入命名空间,即在字符串前面加上xxx/
这种写法,vuex就是使用的这种方法。
export interface action { type: "a/INCREMENT"; payload?: number;}
export const INCREMENT_TYPE = "a/INCREMENT"; // 引入命名空间
export const increment = (nr: number): action => ({ type: INCREMENT_TYPE, payload: nr,});
一种是使用symbol:
export interface action { type: symbol; payload?: number;}
export const INCREMENT_TYPE = Symbol("INCREMENT");
export const increment = (nr: number): action => ({ type: INCREMENT_TYPE, payload: nr,});
这两种方法都可以解决type
有可能重复的问题。
3. 最后
到目前为止,你就可以愉快的在项目中使用Redux了,但是还存在一个缺陷,就是无法处理异步请求,如果需要处理异步请求则需要使用Redux-Saga或者Redux-thunk。
这篇文章所讲述的内容其实都非常基础,但是我看到Redux、React-Redux中还有其它的一些功能,因为我暂且没有使用到所以就暂时不进行讲解了~~(其实是我不会)~~,顺便说一下,更新了hooks的React-Redux是真的非常好用,因为我最开始学习React的时候教程中还是使用的class,当时就觉得对比vuex来说有非常多的不方便的地方。
参考资料: