【React】Redux

Redux是一款著名的JavaScript状态管理容器。

也就是说,Redex除了和React配合使用,还可以配置JS、Vue使用。


1. 设计思想

  • Redux是将整个应用状态存储到一个叫做store的地方,里面存在一个状态树 state tree
  • 组件可通过store.dispatch派发行为actionstore,store不会直接修改state,而是通过用户编写的reducer来生成新的state,并返回给store
  • 其他组件通过订阅 store中的state状态变化来刷新自己的视图

在这里插入图片描述


2. 三大原则

  • 整个应用有且仅有一个store,其内部的state tree存储整个应用的state
  • state是只读的,修改state只能通过派发action,为了描述action如何改变state,编写纯函数reducer(原始状态值,工作)
  • 单一数据源的设计让React组件之间通信更加方便,也有利于状态的统一管理。

3. createStore(reducer)

Redux 提供createStore这个函数,用来生成 Store。

// 目录:./src/store.js
import {createStore} from 'redux';
function reducer(){}
let store = createStore(reudcer)

3.1 store

通过 createStore 方法可以创建一个 store, 需要传递一个参数 reducer。

store提供的方法:

  • store.getState():获取最新的 state tree
  • store.dispatch(): 派发行为 action
  • store.subscribe(): 订阅 store 中 state 的变化

3.2 reducer(state,action)

  • reducer必须是一个纯函数,接收state、action两个参数。
  • state是旧的状态,不可直接修改,需要根据action.type的不同,生成新的state并返回。
// 目录:./src/store.js
import {createStore} from 'redux';
export const ADD = 'ADD';
export const MINUS = "MINUS";
function reducer(state={count:0},action){
    console.log('action:',action);
    switch (action.type){
        case ADD:
            return {count:state.count+1};
        case MINUS:
            return {count:state.count-1};
        default:
            return state;
    }
}
let store = createStore(reducer);
export default store;

3.3 getState() dispatch() subscribe()

//目录: ./src/components/Counter.js
import React from 'react';
import store from '../store';

class Counter extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            number: store.getState().count
        }
    }

    render(){
        return (
            <>
                <p>{this.state.number}</p>
                <button onClick={() => store.dispatch({type: 'ADD'})}>+</button>
                <button onClick={() => store.dispatch({type: 'MINUS'})}>-</button>
            </>
        )
    }
}

export default Counter;

在Counter组件中,通过store.getState()获取最新的state。

点击按钮,会通过store.dispatch()派发action给store(注意:action是一个对象,必须存在type属性),store内部会将当前的state action传递给reducer来生成新的state达到更新状态的目的。

但页面上的数字并没有发生变化。
在这里插入图片描述

可以看到,reducer函数中已经接受了action,此时store中的state已经发生了变化,而页面不更新的原因在于Counter没有订阅store中state的变化。

  • 类组件实现:
//目录: ./src/components/Counter.js
import React from 'react';
import store from '../store';

class Counter extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            number: store.getState().count
        }
    }

    componentDidMount() {
        this.unSubscribe = store.subscribe(() => {
            this.setState({number: store.getState().count})
        })
    }

    componentWillUnmount() {
        this.unSubscribe && this.unSubscribe();
    }


    render(){
        return (
            <>
                <p>{this.state.number}</p>
                <button onClick={() => store.dispatch({type: 'ADD'})}>+</button>
                <button onClick={() => store.dispatch({type: 'MINUS'})}>-</button>
            </>
        )
    }
}

export default Counter;
  • 函数组件实现:
//目录: ./src/components/Counter.js
import React from 'react';
import store from '../store';
import {useState,useEffect} from 'react';

function Counter (){
    let [number,setNumber] = useState(store.getState().count);

    useEffect(() => {
        const unSubscribe = store.subscribe(() => {
            setNumber(store.getState().count)
        })

        return () => {
            unSubscribe();
        }
    },[])

    return (
        <>
            <p>{number}</p>
            <button onClick={() => store.dispatch({type: 'ADD'})}>+</button>
            <button onClick={() => store.dispatch({type: 'MINUS'})}>-</button>
        </>
    )

}

export default Counter;

在这里插入图片描述
使用store.subscribe()就可以实现订阅,该方法接受一函数,当store中state的状态发生改变,就会执行传入的函数。

store.subscribe()方法返回一个函数,用于取消订阅。

首次加载后,控制台输出了:
在这里插入图片描述
这是store为了拿到state的初始值{count:0},会自动派发一次action {type: “@@redux/INIT1.s.m.m.c.n”}

redux内部使用"发布-订阅"模式

3.4 bindActionCreators()

在 Counter 组件中,我们是直接使用 store.dispatch 派发action

<button onClick={ ()=> store.dispatch({type: 'ADD'})}>count++</button>
<button onClick={ ()=> store.dispatch({type: 'MINUS'})}>count--</button>

上面写法的缺陷在于,多次重复写了 store.dispatch, 并且 action.type 容易写错还不易发现,此时 redux 提供了 bindActionCreators 功能,将派发 action 的函数与 store.dispatch 进行绑定。

import React from 'react';
import store from '../store';
import {useState,useEffect} from 'react';
import {bindActionCreators} from "redux";

function Counter (){
    let [number,setNumber] = useState(store.getState().count);

    useEffect(() => {
        const unSubscribe = store.subscribe(() => {
            setNumber(store.getState().count)
        })

        return () => {
            unSubscribe();
        }
    },[])

    function add() {
        return {type:'ADD'}
    }

    function minus(){
        return {type:'MINUS'}
    }

    const bindAdd = bindActionCreators(add,store.dispatch);
    const bindMinus = bindActionCreators(minus,store.dispatch);


    return (
        <>
            <p>{number}</p>
            <button onClick={bindAdd}>+</button>
            <button onClick={bindMinus}>-</button>
        </>
    )

}

export default Counter;

其实,代码中可将 bindActionCreators 逻辑抽离到单独文件中,可在其它组件中去使用。

同时,上面代码的缺陷在与 每个函数都需要去手动绑定,并不合理,所以,bindActionCreators 支持传入对象,将所以的 actionCreator 函数包装成对象,代码如下:

import React from 'react';
import store from '../store';
import {useState,useEffect} from 'react';
import {bindActionCreators} from "redux";

function Counter (){
    let [number,setNumber] = useState(store.getState().count);

    useEffect(() => {
        const unSubscribe = store.subscribe(() => {
            setNumber(store.getState().count)
        })

        return () => {
            unSubscribe();
        }
    },[])

    function add() {
        return {type:'ADD'}
    }

    function minus(){
        return {type:'MINUS'}
    }

    const actions = {add,minus};

    const bindActions = bindActionCreators(actions,store.dispatch);

    return (
        <>
            <p>{number}</p>
            <button onClick={bindActions.add}>+</button>
            <button onClick={bindActions.minus}>-</button>
        </>
    )
}

export default Counter;

4. 购物车案例

举例一个场景:
我在逛淘宝商城,点进了一个淘宝店家的商品A的详情页,加进购物车;又点进了天猫店家的商品B,加入购物车。

最终我打开购物车发现有A B两件商品。假设淘宝商品详情页和天猫商品是两个独立的组件。

要实现上述的场景,购物车就应该是一个共享组件。此时,可以使用redux对购物车进行统一管理。

  • 目录结构
    在这里插入图片描述
  • store.js
import {createStore} from 'redux';

// 声明一个实际操作函数,相应操作
const cardReducer = (state={goods:[]},action) => {
    switch(action.type){
        case 'add':
            return {...state,goods:[...state.goods,action.good]}
        case 'delete':
            let deletedGoodIdx = action.good.index;
            let newGoods = [...state.goods];
            newGoods.splice(deletedGoodIdx,1);
            return {...state,goods:newGoods};
        default:
            return state;
    }
}

// 创建store实例并导出
const store = createStore(cardReducer);
export default store;
  • 淘宝----TBItem.js
import store from '../store/store';

// 淘宝商品详情组件
function TBItem(){
    return (
        <>
            <h2>这是淘宝详情页</h2>
            <p>当前购物车内商品数量: {store.getState().goods.length}</p>
            <button
                onClick={() => {
                    store.dispatch(
                        {
                            type:'add',
                            good:{
                                title:`淘宝商品${Date.now()}`,
                                price: 100
                            }
                        })

                }}>添加购物车
            </button>
        </>
    )
}

export default TBItem;
  • 天猫—TMItem.js
import store from '../store/store';

// 天猫商品详情组件
function TMItem(){
    return (
        <>
            <h2>这是天猫详情页</h2>
            <p>当前购物车商品数量:{store.getState().goods.length}</p>
            <button
                onClick={() => {
                    store.dispatch(
                        {
                            type:'add',
                            good:{
                                title:`天猫商品${Date.now()}`,
                                price: 100
                            }
                        })

                }}>添加购物车
            </button>
        </>
    )
}

export default TMItem;
  • 购物车—Cart.js
import store from '../store/store';

function Cart(){
    let goods = store.getState().goods;
    return (
        <>
            <ul>
                {
                    goods.map((good,index)=> {
                        return (
                            <li key={ index }>
                                <span>{ good.title }</span>
                                &nbsp;&nbsp;&nbsp;&nbsp;
                                <button onClick={ ()=>
                                    store.dispatch({
                                        type:'delete',
                                        good:{index}
                                    })}>删除</button>
                            </li>
                        )
                    })
                }
            </ul>
        </>
    )
}
export default Cart;
  • App.js
import {NavLink,Route,Routes} from 'react-router-dom';
import TBItem from './components/TBItem';
import TMItem from './components/TMItem';
import Cart from './components/Cart';


function App() {
  return (
    <div className="App">

      <div>
        <NavLink to={'/tb'}>淘宝</NavLink>
        <br/>
        <NavLink to={'/tm'}>天猫</NavLink>
      </div>

      <Routes>
        <Route path={'/tb'} element={<TBItem/>}/>
        <Route path={'/tm'} element={<TMItem/>}/>
      </Routes>

      <Cart/>

    </div>
  );
}

export default App;
  • index.js
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';

import store from './store/store';
import {BrowserRouter} from 'react-router-dom';

const render = () => {
    ReactDOM.render(
        <BrowserRouter>
            <App />
        </BrowserRouter>,
        document.getElementById('root')
    );
}

render();
store.subscribe(render);

最终效果展示:

  • 刚开始进入
    在这里插入图片描述

  • 点击进入淘宝界面添加商品
    在这里插入图片描述

  • 点击进入天猫界面添加商品
    在这里插入图片描述
    由此可以发现淘宝天猫添加的商品都在购物车中都能展现。
    实现了使用redux对淘宝天猫状态的统一管理。

在这里插入图片描述


5. 小结

可以看出,在 React 组件中使用 store, 都需要手动去引入 store 文件, 手动订阅 store 中状态的变化,这是不合理的。
下篇博客,我们看 react-redux 是如何解决的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南栀~zmt

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值