使用过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 + 1
store.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来说有非常多的不方便的地方。
参考资料:
React-Redux Hooks
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!