BaKa也能看懂的react + redux和react-router + redux教程

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

redux

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

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

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

file

  • 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)

在使用过程中,先通过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

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的上下文中

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的组件进行包装

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上。

安装

安装必要的库

yarn add react-redux
yarn add react-router
yarn add connected-react-router

配置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

配置根组件

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组件

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了

点赞

发表评论

昵称和uid可以选填一个,填邮箱必填(留言回复后将会发邮件给你)
tips:输入uid可以快速获得你的昵称和头像

Title - Artist
0:00