BaKa也能看懂的react-router和redux教程
        
 # 序
这是一个非常基础的redux教程,在我学redux时,发现无论是官方文档还是其他的博客,均会先讲一堆redux的设计思想,然后抛出一堆新概念看着脑壳疼,所以我打算写一篇非常基础的教程,没有花里花哨的,只是讲讲最简单的使用,至于思想啥的,掘金一搜一大把啦。所以这篇文章只是追求用最简单的例子来介绍API,不会讲的太深,就酱。
# redux
redux和react没有太大的关系,它只是一个单独的库,你可以随便在哪个地方使用它,它的主要用途我觉得有两点
- 便于多组件跨级间共享数据
 - 便于提供规范统一的数据源管理
 
先用一张图介绍redux中的几个重要角色

- store:顾名思义,是用来保存数据的
 - action:redux不允许直接操作数据,修改仓库的数据需要派发一个action,这个action用于标识怎么修改数据
 - reducer:reducer用于根据action的类型来对数据进行具体的处理
 
我们来看一个简单的例子
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)
 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
在使用过程中,先通过createStore创建一个store,传入的两个参数依次是reducer(也就是具体根据action处理数据的方法) 和state的默认值
然后通过store.subscribe订阅state的变化
最后我们可以通过store.dispatch派发action,派发的action会被传入到reducer中进行处理

那么redux的简单使用就介绍完了。
# react + redux
这里我们用一个简单的例子来介绍使用,我们要做一个可以通过按+或-来控制上方显示的计数器
# 安装react-redux
要把react和redux结合起来,我们要使用一个库react-redux
安装:npm install react-redux --save
# 配置store
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});
 2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 在外围组件中配置Provider
在这里我们在根组件中中配置Provider,也就是把Counter组件放到redux的上下文中
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>
);
 2
3
4
5
6
7
8
9
10
11
12
# 使用connect对redux的组件进行包装
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;
 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
这里使用了connect这个函数,这个函数在第一次调用时会返回一个高阶组件用于包装组件,如果你不知道什么是高阶组件也不要紧,你只要记住需要连续调用两次就行了,第一次传入两个函数,第二次传入要包装的组件
其中第一个函数mapStateToProps用于把store中的state映射到props上,也就是这个函数返回的值会和props先进行一次混合再传给我们的Counter组件,这样我们的Counter组件就可以通过props拿到store中的内容了
第二个函数mapDispatchToProps用于封装要用到的action,这是为了确保组件里不能乱派发action,所以集合起来统一管理,同样的,返回的对象同样会和props混合后传给Counter组件
最后我们把包装后的组件导出就好啦
# react-router + redux
如果我们要把router和redux中的某些数据进行联动,我们就可以把router的数据放到redux上。
# 安装
安装必要的库
yarn add react-redux
yarn add react-router
yarn add connected-react-router
 2
3
# 配置store
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
 2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 配置根组件
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")
)
 2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 设置App组件
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);
 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
# 测试一下
我们分别点击按钮A和B

可以看到router的变化已经被同步到redux了
- 01
 - 009-Palindrome Number[回文数]03-10
 
- 02
 - 008-String to Integer (atoi)[字符串转整数]03-10
 
- 03
 - 004-Reverse-integer[整数反转]03-09