一、什么是redux?
之前写过react组件内部的state,react传参数是父亲传儿子,儿子再传他儿子这样一级一级的就跟办手续似的。超级麻烦还累。所以就用到了redux。
官网上说:Redux is a predictable JavaScript state container。
Redux 是 JavaScript 状态容器,提供可预测化的状态管理。
提供了“数据的单向流动”:
组件 → action → reducer → 其他组件
redux就是全局数据,采用了dispatch才能改变数据的这样的一个机制。
不懂???不懂就用一用嘛。用一用就懂了。
二、react结合redux
1. 在react中使用redux要先安装两个依赖:
npm install --save redux
npm install --save react-redux
- react-redux就是粘合剂,让redux可以和react共同使用的东西
先来一份初始的目录结构: test |-node_modules |-www |-app |-main.js |-App.js |-index.html |-webpack.config.js |-package.json
2. 创建reducers文件夹,创建index.js文件,这个文件向外暴露一个纯函数:
目录改变如下:
修改后如下: test |-node_modules |-www |-app |-main.js |-App.js |-reducers |-index.js |-index.html |-webpack.config.js |-package.json
reducers/index.js代码如下:
export default (state = {"v" : 100} , action) => {
// 这个函数的第一个参数就是我们的数据了
// 第二个参数是干什么用的往下看就知道了
// 这个是必须写的!!
return state;
}
- 开头就说了,react传个数据跟办手续似的,所以我们就用redux将数据放到全局去,让所有组件都可以去调用他。而我现在创建的reducers文件夹,说白了就是我们存放数据的地方。
3.改变main.js 和 App.js
main.js中做的事情:
①引入redux
②创建store
③通过Provider分发者,将store分发给所有的组件
App.js中做的事:
①从react-redux包中引入了{connect}
②通过connect关联reducers中的数据
③用this.props.**使用数据
④通过this.props.**()事件发送dispatch指令
main.js代码如下:
import React from "react";
import ReactDOM from "react-dom";
//引入createStore和Provider
import {createStore} from "redux";
import {Provider} from "react-redux";
//引入子组件App
import App from "./App.js";
//引入我们放数据的reducers
import reducer from "./reducers";
// 创建store store就跟个传达室似的 将store传给组件之后
// 组件中才有使用this.store.props.**传达指令的权限
// 然后就可以通过store获取到与其关联子组件关联数据
const store = createStore(reducer);
ReactDOM.render(
//通过Provider 将store传输给所有的组件 就是让所有的组件拥有了this.store.props这个技能
<Provider store={store}>
<App></App>
</Provider>
,
document.getElementById("app")
)
App.js代码如下:
import React from "react";
// 从react-redux包中引入了{connect},干什么用的?
// 直接翻到最下边看connect怎么用
import {connect} from "react-redux";
class App extends React.Component{
constructor(){
super();
}
render(){
return <div>
// 在这里用this.props.v
// 还记得我们在reducers文件夹中的index.js中暴露的纯函数不
// 就是那里的数据 所以这里就是<h1>100</h1>
// 这里应该是不能写注释的,用代码请删注释,下同
<h1>{this.props.v}</h1>
// 然后我们发送onClick事件,发送this.props.add()函数,接下来我们到最下边的connect中看
<button onClick={()=>{
this.props.add()
}}>按我加1</button>
// 这里发送了this.props.minus()函数
<button onClick={()=>{
this.props.minus()
}}>按我减2</button>
</div>
}
}
// 通过connect与reducers中的数据产生关联
// 然后才能通过刚刚main.js分配过来的store使用this.store.props.**指令在render中使用数据
//然后我们现在就去render里看看
// 注意默认暴露connect 一个组件中只能有一个暴露,所以App类前的暴露记得去掉!否则会报错!
// connect写法:export default connect()(这里是组件的名称App里就是App)
export default connect(
// 这里是你要用到的state数据
(state) => ({
v : state.v
}),
// 我们在connect中发送dispatch指令,这个指令的作用是修改数据的,type,n是载荷,可以看到载荷可以多个type必须有!
// 就是通过dispatch指令发送的载荷,可以在reducers中,就是放数据的地方,还记得不!!
// 可以在reducers中通过action.**来调用,比如action.type就是"ADD"
// action就是reducers中index.js里边的第二个参数,还有印象不!
// 具体是咋回事?往下看往下看
(dispatch) => ({
add(){
dispatch({"type" : "ADD"})
},
minus(){
dispatch({"type" : "MINUS",n:"2"})
},
})
)(App);
4.数据的改变
我们不是发送了个dispatch吗,然后带了个type吗。有啥用呢???
我们回到reducers/index.js,并修改它,
reducers/index.js修改后代码如下:
export default (state = {"v" : 100} , action) => {
// 当点击按我加1时发送this.props.add()函数,然后触发dispatch({"type" : "ADD"})
// 通过action.type得到之后进行如下判断,return了{"v" : state.v + 1}
// 当点击按我减2的发送this.props.minus()函数,dispatch({"type" : "MINUS",n:"2"})
//通过action.type得到之后进行如下判断,return了{ "v": state.v - action.n} action.n就是2
if(action.type == "ADD"){
return {
"v" : state.v + 1
}
} else if (action.type == "MINUS") {
return {
"v": state.v - action.n
}
}
return state;
}
- 现在感觉redux饶了好大一个弯,实际上是因为业务小。
- 如果业务大了,真的简化了程序。
- 而且你可在reducers里看到所有可能的数据改变 ,这就是最开始提到的提供可预测化的状态管理。
三、一点小改变
之前我组件中的state时提过,react编程必须养成的思维:
组件只有父子之间有关系,兄弟和兄弟之间没有任何关系!
兄弟和兄弟之间不需要考虑彼此对彼此造成的任何的影响,只需要考虑对父亲的影响即可。
而现在使用了redux后:
所有组件只对全局数据(store中的state)负责,不需要对其他组件负责。
如果要改变全局数据,请dispatch一个action。
四、redux套路小结
- 首先创建一个reducers文件夹 里边存数据
- 在main.js 中创建store通过
将store传递给所有的组件 - 在组件中引入connect 并使用connect使组件与reducers里的数据产生关联 得到要用的数据
- 通过this.props.** 使用数据
- 发送事件,触发dispatch命令发送type,也可以携带多个载荷
- 在reducers通过对action.type的值判断来对数据进行修改
五、combineReducers和bindActionCreators
combineReducers和bindActionCreators会使文件结构再次复杂,但是体系更完整。没什么好怕的。
从上边可以看出来套路都是很简短的。
先说说为什么要用这俩东西。
在一个项目中会有许多组件,但是store只能有一个。不同的组件不可能数据都用一样的,或者累的一大堆。
所以虽然我们store不能拆,但是reducers是可以拆分的。
这样的好处就是让我们项目的数据结构更加清晰,这时候就用到了combineReducers。
同理,我们的dispatch也分门别类不是更好,于是就用到了bindActionCreators。
知道这些就可以开始下边的操作了。
抛开上边的文件结构,我们重来一份。
同样先来一份初始的目录结构: test |-node_modules |-www |-app |-main.js |-App.js |-index.html |-webpack.config.js |-package.json
combineReducers拆分reducers
1.创建reducers
创建reducers文件,并创建index.js,counterReducer.js,shoppingReducer.js
修改后的目录结构: test |-node_modules |-www |-app |-main.js |-App.js |-reducers |-index.js |-counterReducer.js |-shoppingReducer.js |-index.html |-webpack.config.js |-package.json
reducers/index.js代码如下:
这个时候,index.js不再是像上边一样的数据文件了
其实就变成了一个reducers中所有数据文件的一个中心管理处
都是固定的用法
// 引入combineReducers
import {combineReducers} from "redux";
// 引入数据
import counterReducer from "./counterReducer.js";
import shoppingReducer from "./shoppingReducer.js";
//数据管理中心 就相当于state={counterReducer,shoppingReducer}
export default combineReducers({
counterReducer,
shoppingReducer
});
reducers/counterReducer.js代码如下:
这里用了switch语句,跟if同理,别看不懂了
export default (state = { "v": 100 }, action) => {
switch (action.type) {
case "ADD": {
return {
"v": state.v + 1
}
}
}
return state;
}
reducers/counterReducer.js代码如下:
export default (state = { "price": 1000 }, action) => {
switch (action.type) {
case "UP": {
return {
"price": state.price + 1000
}
}
}
return state;
}
2.main.js同上没有改变
3.组件要改变:
App.js代码如下:
import React from "react";
import {connect} from "react-redux";
class App extends React.Component{
constructor(){
super();
}
render(){
return <div>
<h1>{this.props.v}</h1>
<button onClick={()=>{
this.props.add();
}}>按我加1</button>
</div>
}
}
export default connect(
//这里原本是state,state现在不是变成state={counterReducer,shoppingReducer}
//所有这里是一种结构的写法,此时就可以调用counterReducer.js中的v
//注意写法!调用shoppingReducer也是同理了。
({counterReducer}) => ({
v : counterReducer.v
}),
(dispatch) => ({
add(){
dispatch({"type" : "ADD"})
}
})
)(App);
这就实现了reducers的拆分了。接下来就要拆dispatch发送的action了。
提炼aciton文件
具体操作:创建actions文件夹,创建actions/counter.js文件:
修改后的目录结构: test |-node_modules |-www |-app |-main.js |-App.js |-reducers |-index.js |-counterReducer.js |-shoppingReducer.js |-actions |-counter.js |-shopping.js |-index.html |-webpack.config.js |-package.json
actions/counter.js:
export const add = () => ({"type" : "ADD"}); //action creator
export const minus = () => ({"type" : "MINUS"}); //action creator
actions/shopping.js:
export const add = () => ({"type" : "UP"}); //action creator
export const minus = () => ({"type" : "DOWN"}); //action creator
- 这个文件向外暴露了两个函数,函数运行之后将返回action。返回action的函数我们称为action creator。
组件要改变写法(刻意的背一下就行了,都是繁文缛节,没必要去看API,更没必要研究机理):
App.js:
import React from "react";
import {connect} from "react-redux";
// 引入bindActionCreators
import {bindActionCreators} from "redux";
// 引入刚刚创建的action creator 叫conter
import * as counter from "./actions/counter.js";
class App extends React.Component{
constructor(){
super();
}
render(){
return <div>
<h1>
{this.props.v}
</h1>
<button onClick={()=>{
//这里的写法改变了,调用下边定义的counterCreators
// 因为你counterCreators使用bindActionCreators绑定到了counter中
// 如下调用里头的add就发送了({"type" : "ADD"}); 看actions/counter.js:
this.props.counterCreators.add();
}}>点击我加1</button>
<button onClick={()=>{
//这里也是
this.props.counterCreators.minus();
}}>点击我减1</button>
</div>
}
}
export default connect(
({counterReducer})=>({
v : counterReducer.v
}),
(dispatch) => ({
// 这里定义dispatch counterCreators是自己定的名字 绑定到counter中
// 固定写法,counter就是你自己定义actions下的文件counter.js 在上边引入的
//如果想使用shopping里的action creator就把counter改成shopping
counterCreators: bindActionCreators(counter , dispatch)
})
)(App);
六、最后再来捋一遍
- 首先创建一个reducers文件夹 里边存数据 将数据分类 然后用combineReducers 在reducers/index.js下进行综合管理
- 在main.js 中创建store通过
将store传递给所有的组件 - 在组件中引入connect 并使用connect使组件与reducers里的数据产生关联 得到要用的数据
- state变成了一个对象,存储着所有的数据文件,结构state,通过类似counterReducers.v的方式,引入自己要用的数据
- 通过类似this.counterReducers.props.** 使用数据 其实就像是有了一个命名空间
- 通过自定义一个方法名字counterCreators,用bindActionCreators绑定到要使用的actions下的相应文件
- 通过类似this.counterCreators.add()的方法发送dispatch指令
- 指令就会找到对应的actions/count.js下去找add,并发送action({“type” : “ADD”});
- 在reducers通过对action.type的值判断来对数据进行修改