这是一个非常基础的redux教程,在我学redux时,发现无论是官方文档还是其他的博客,均会先讲一堆redux的设计思想,然后抛出一堆新概念看着脑壳疼,所以我打算写一篇非常基础的教程,没有花里花哨的,只是讲讲最简单的使用,至于思想啥的,掘金一搜一大把啦。所以这篇文章只是追求用最简单的例子来介绍API,不会讲的太深,就酱。

redux

redux和react没有太大的关系,它只是一个单独的库,你可以随便在哪个地方使用它,它的主要用途我觉得有两点

  • 便于多组件跨级间共享数据
  • 便于提供规范统一的数据源管理

先用一张图介绍redux中的几个重要角色

file

  • store:顾名思义,是用来保存数据的
  • action:redux不允许直接操作数据,修改仓库的数据需要派发一个action,这个action用于标识怎么修改数据
  • reducer:reducer用于根据action的类型来对数据进行具体的处理

我们来看一个简单的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import {createStore} from "redux";

function reducer(state, action) {
let newState = {
...state
}
// 根据type对状态进行处理
switch (action.type) {
case "add":
newState.num++
break;
default:
break;
}
// 返回新的状态
return newState;
}

// 创建仓库
let store = createStore(reducer, {
num : 0
});

// 数据更新时触发的回调
const handleStoreUpdate = function () {
console.log(store.getState());
}

// 注册数据更新时触发的函数
store.subscribe(handleStoreUpdate);


setTimeout(() => {
// 派发action修改数据
store.dispatch({
type : "add"
})
}, 1000)

在使用过程中,先通过createStore创建一个store,传入的两个参数依次是reducer(也就是具体根据action处理数据的方法) 和state的默认值

然后通过store.subscribe订阅state的变化

最后我们可以通过store.dispatch派发action,派发的action会被传入到reducer中进行处理

file

那么redux的简单使用就介绍完了。

react + redux

这里我们用一个简单的例子来介绍使用,我们要做一个可以通过按+或-来控制上方显示的计数器

安装react-redux

要把react和redux结合起来,我们要使用一个库react-redux

安装:npm install react-redux --save

配置store

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const reducer = function (state, action) {
let num = state.num;
switch (action.type) {
case "increase":
num++;
break;
case "decrease":
num--;
break;
default:
break;
}
return {
...state,
num
}
}
let store = createStore(reducer, {num : 0});

在外围组件中配置Provider

在这里我们在根组件中中配置Provider,也就是把Counter组件放到redux的上下文中

1
2
3
4
5
6
7
8
9
10
11
12
import React from 'react';
import ReactDOM from 'react-dom';
import {store} from "./redux/index.js";
import {Provider} from "react-redux";
import Counter from "./components/Test";

const rootElement = document.getElementById('root');
const renderElement = (
<Provider store={store}>
<Counter/>
</Provider>
);

使用connect对redux的组件进行包装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import React from "react";
import {connect} from "react-redux";

function Counter(props) {
return (
<div>
<h2>{JSON.stringify(props.num)}</h2>
<button onClick={props.increase}>+</button>
<button onClick={props.decrease}>-</button>
</div>
)
}

function mapStateToProps(state) {
return {
num : state.num
}
}
function mapDispatchToProps(dispatch) {
return {
increase() {
dispatch({type : "increase"})
},
decrease() {
dispatch({type : "decrease"});
}
}
}

let CounterContainer = connect(mapStateToProps, mapDispatchToProps)(Counter);

export default CounterContainer;

这里使用了connect这个函数,这个函数在第一次调用时会返回一个高阶组件用于包装组件,如果你不知道什么是高阶组件也不要紧,你只要记住需要连续调用两次就行了,第一次传入两个函数,第二次传入要包装的组件

其中第一个函数mapStateToProps用于把store中的state映射到props上,也就是这个函数返回的值会和props先进行一次混合再传给我们的Counter组件,这样我们的Counter组件就可以通过props拿到store中的内容了

第二个函数mapDispatchToProps用于封装要用到的action,这是为了确保组件里不能乱派发action,所以集合起来统一管理,同样的,返回的对象同样会和props混合后传给Counter组件

最后我们把包装后的组件导出就好啦

react-router + redux

如果我们要把router和redux中的某些数据进行联动,我们就可以把router的数据放到redux上。

安装

安装必要的库

1
2
3
yarn add react-redux
yarn add react-router
yarn add connected-react-router

配置store

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import thunk from 'redux-thunk';  // 这个中间件可以让action为函数
import {createBrowserHistory} from 'history';
import {createStore, applyMiddleware, combineReducers} from 'redux'
import { connectRouter, routerMiddleware } from 'connected-react-router'
import { composeWithDevTools } from 'redux-devtools-extension';

export const history = createBrowserHistory(); // 浏览器端应用使用createBrowserHistory来创建history对象
const initialState = {};
const reducers = combineReducers({
router: connectRouter(history), // 合并router-redux联动需要的reducer
})
const store = createStore(
connectRouter(history)(reducers), // 使用connectRouter包裹reducer
initialState,
composeWithDevTools(applyMiddleware(thunk, routerMiddleware(history)))
// composeWithDevTools是调试用的中间件(如果你不需要可以去掉)
// routerMiddleware让路由通过dispatch(push('/url'))来变化
);

store.subscribe(() => {
console.log(store.getState())
})

export default store

配置根组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { ConnectedRouter } from 'connected-react-router'
import "./App.css"
import App from './App';
import store from './redux/store';
import { history } from './redux/store';

// 使用ConnectedRouter代替Router,并且将store中创建的history对象引入
render(
<Provider store={store}>
<ConnectedRouter history={history}>
<App/>
</ConnectedRouter>
</Provider>,
document.querySelector("#root")
)

设置App组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import React from 'react'
import {connect} from "react-redux";
import {push} from 'connected-react-router';
import {Route, Switch} from 'react-router'; // react-router v4/v5

function A() {
return <h1>组件A</h1>
}


function B() {
return <h1>组件B</h1>
}

function App(props) {
return (
<>
<Switch>
<Route path="/b" component={B} />
<Route path="/a" component={A} />
</Switch>
<div className={"button-box"}>
<button onClick={props.toA}>A</button>
<button onClick={props.toB}>B</button>
</div>
</>
)
}

function mapStateToProps(state) {
return {

}
}
function mapDispatchToProps(dispatch) {
return {
// 使用dispath切换路由
toA() {
dispatch(push('/a'))
},
toB() {
dispatch(push('/b'));
}
}
}

export default connect(mapStateToProps, mapDispatchToProps)(App);

测试一下

我们分别点击按钮A和B

file

可以看到router的变化已经被同步到redux了