前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React全家桶之Redux使用

React全家桶之Redux使用

作者头像
一粒小麦
发布2019-07-18 17:17:21
1.3K0
发布2019-07-18 17:17:21
举报
文章被收录于专栏:一Li小麦

使用redux

让我们闭上眼睛想想,如果用一个词描述React 和Redux 给我们留下了什么印象,我想到的不是难学,不是繁琐,而是“限制”。

“限制”在这里绝不是贬义词,恰恰相反,是对技术框架的最高夸奖,因为限制能够确保程序按照可控的方式进化。

在计算机软件世界里,造物主就是人类自己,没有物理化学的限制,一切皆有可能。也正因为一切皆有可能,一个问题即使没有无数种解法,也会有很多很多种解法。

但是,拥有很多方案并不表示我们应该使用所有的方案。

软件要由程序员来维护和开发,研发部门管理也是程序员。而程序员是人,不是机器。当负担多个开发任务的时候,牵一发而动全身,bug 层出不穷,即使最专业的程序员,我想也会丧失勇气吧。

React和Redux技术框架最大的好处,并不是让我们无所不能,而是设定了一规矩,让每个模块只做最单一的事情。让开发者只能按照这套规矩来完成代码。这样,只要理解了这套规矩,无论产生的代码由谁来维护由谁来继续开发都不会有大问题。

redux其实借鉴与flux的思想,它是单向数据流的最佳实践(也许吧)。

和vuex的区别: 没有getters和actions,仅仅关注状态的变更。更加纯粹(dispatch),vuex包括dispatch和commit。 而且redux的dispatch是同步操作。redux并非react独有,适用范围非常广。但vuex高度依赖于vue。

本文将基于上一讲的水果购物车(Hook.js)继续开发。再次展示一段代码重构的过程。

源代码的注释里阐述了redux三大原则:

代码语言:javascript
复制
* Creates a Redux store that holds the state tree.
 * The only way to change the data in the store is to call `dispatch()` on it.
 * There should only be a single store in your app. To specify how different
 * parts of the state tree respond to actions, you may combine several
 * reducers

应用中所有的 state 都以一个对象树的形式储存在一个单一的 store 中。 惟一改变 state 的办法是触发 action,一个描述发生什么的对象。 为了描述 action 如何改变 state 树,你需要编写 reducers。

安装:

代码语言:javascript
复制
npm i redux react-redux -S

在react下,还需要创建reac相关依赖

代码语言:javascript
复制
npm install --save react-redux
npm install --save-dev redux-devtools

创建 store实例,在根组件比如 App.js中注册store,通过上下文(react-redux提供的Provider)的方式注入进去。

创建store实例:createStore

createState创建了状态并储存。全局应用中只能有一个。创建一个 store.js

store同时必须对应一个 reducer函数:他接收两个参数,分别是当前的 state 树和要处理的 action,返回新的 state 树。

代码语言:javascript
复制
import {createStore} from 'redux'

function fruitReducer(state={
    list:[]
}, action) {
    switch (action.type) {
        case "init": // 初始化fruits
            return {state,list:action.payload}
        case "add": // 新增
            return {...state, list:[...state.list,action.payload]};
        default:
            return state;
    }
}

//创建一个全局实例
const store = createStore(fruitReducer)
export default store

Store的返回值: 保存了应用所有 state 的对象。改变 state 的惟一方法是 dispatch action。你也可以 subscribe 监听 state 的变化,然后更新 UI。

代码语言:javascript
复制
// 可以手动订阅更新,也可以事件绑定到视图层。
store.subscribe(() =>
  console.log(store.getState())
);
注册provider

provider是为组件提供上下文环境。在根组件中

代码语言:javascript
复制
import { Provider } from "react-redux";
import store from "./store";
import ReduxTest from "./ReduxTest";
...
<Provider store={store}>
  <ReduxTest />
</Provider>

#### 使用状态映射(connect)

store的状态,如何正确反映到组件中,dispatch如何调用?这需要react-redux提供的另外一个函数:connect

代码语言:javascript
复制
connect(state=>({
    fruits:state.list,
}))(原来的函数组件)

原来的函数组件,映射出来,自动带上了各种状态,包括dispatch方法。(接收的这两个参数)

代码语言:javascript
复制
import React, { useState } from 'react';
import { useEffect } from "react";

import { connect } from 'react-redux'

// 水果列表
function FruitList({ fruits, setFruit }) {
    let result = fruits.map(f => <li onClick={() => { setFruit(f) }} key={f}>{f}</li>)
    return result;
}

// 添加水果
function AddFruit({ onAddFruit, fruits }) {
    return (
        <input type='text' onKeyUp={(e) => {
            if (e.keyCode == 13) {
                onAddFruit(e.target.value)
                e.target.value = '';
            }
            e.persist()
        }} />
    )
}


export default  connect(state=>({
    fruits:state.list,
}))(function HooksTest({fruits,dispatch}) {
    console.log(1,fruits)
    const [fruit, setFruit] = useState("草莓");
    // 全局属性
    useEffect(() => {
        setTimeout(() => {
            // 变更状态,提交
            dispatch({ type: "init", payload: ["香蕉", "西瓜"] });
        }, 1000);
    }, []);
    return (

        <div>
            <p>{fruit === "" ? "请选择喜爱的水果:" : `您的选择是:${fruit}`}</p>
            <AddFruit onAddFruit={pname => dispatch({ type: 'add', payload: pname })} fruits={fruits}></AddFruit>
            <FruitList setFruit={setFruit} fruits={fruits}></FruitList>
        </div>
    );
})

如果子组件也需要,就再创建一个connect。即可。

重构

当前代码很不友好,应该重构一下。

重点思考,connect的两个参数是什么含义?

在组件中dispatch操作(add,init)会造成很大的耦合。如果能结构到组件的参数中,就好了。

首先用一个语义化的函数名指代第一个参数:

代码语言:javascript
复制
//给包装的组件传属性
const mapStateToProps=state=>({
    fruits:state.list,
})

第二个参数本质上是一个actionCreater。它是一个对象,声称了了你想定义的action操作。

代码语言:javascript
复制
// store.js
// action-creater
export const init=(payload)=>({
    type:'init',
    payload
})

export const addFruit=(payload)=>({
    type:'add',
    payload
})

// 组件.js
import {addFruit,init} from '../store'
const mapDispatchToProps={
    init,addFruit
}

export default  connect(mapStateToProps,mapDispatchToProps)(function HooksTest({fruits,init,addFruit}) {
    const [fruit, setFruit] = useState("草莓");
    useEffect(() => {
        setTimeout(() => {
            init(["香蕉", "西瓜"])
        }, 1000);
    }, []);
    return (

        <div>
            <p>{fruit === "" ? "请选择喜爱的水果:" : `您的选择是:${fruit}`}</p>
            <AddFruit onAddFruit={(payload)=>addFruit(payload)} fruits={fruits}></AddFruit>
            <FruitList setFruit={setFruit} fruits={fruits}></FruitList>
        </div>
    );
})

改完之后的代码语意清晰,春风拂面,不用注视即可看懂,简直是redux操作中的一股清流

异步处理

redux是不支持异步的。如果需要异步,需装中间件。redux中间件概念:

派发的action本来是直接到中间件中的。但经过中间件(强化器)处理后,可以做异步操作,或者打日志

  1. 安装redux-thunk和redux-logger: npm i redux-thunk redux-logger-S
  2. 应用中间件,store.js 中
代码语言:javascript
复制
import { createStore, applyMiddleware } from "redux";
import logger from "redux-logger";
import thunk from "redux-thunk";
// 对store应用中间件,注意有先后顺序
const store = createStore(fruitReducer, applyMiddleware(logger, thunk));
  1. 定义异步动作
代码语言:javascript
复制
// store
// 在把异步请求的动作放到一个异步操作中。
export const asyncFetch = (payload) => {
  return dispatch => {
    setTimeout(() => {
                dispatch({type:'init', payload: ["草莓", "香蕉"]}); }, 1000);
  }; 
};
  1. 使用(hook.js)

那么原来的init都可以不要了。

代码语言:javascript
复制
import {addFruit,asyncFetch} from '../store'
const mapDispatchToProps = {
    asyncFetch,
    addFruit
};

function HooksTest({fruits,addFruit,asyncFetch}) {
    const [fruit, setFruit] = useState("草莓");
    // 全局属性
    useEffect(() => {
        asyncFetch(["草莓", "香蕉"] )
    }, []);
    return (
        <div>
            <p>{fruit === "" ? "请选择喜爱的水果:" : `您的选择是:${fruit}`}</p>
            <AddFruit onAddFruit={(payload)=>addFruit(payload)} fruits={fruits}></AddFruit>
            <FruitList setFruit={setFruit} fruits={fruits}></FruitList>
        </div>
    );
}

export default  connect(mapStateToProps,mapDispatchToProps)(HooksTest)

你用类似同步的写法,轻易地写出了功能,异步操作的日志也被轻易地打印出来了。

模块化(combineReducers)

当前尽管hook.js已经非常好读,但是store还是一团糟。应该考虑把hook相关的逻辑(reducer)从是store中分离。

首先,在store文件夹下新建一个 fruitReducer.js,把无关store本身的业务逻辑

代码语言:javascript
复制
// 把action和reducer移至fruit.redux.js
// 导出异步操作
export const asyncFetch = (payload) => {
    return dispatch => {
        setTimeout(() => {
            dispatch({ type: 'init', payload});
        }, 1000);
    };
};

// action-creater
export const init = (payload) => ({
    type: 'init',
    payload
})

export const addFruit = (payload) => ({
    type: 'add',
    payload
})

export default function(state = {
    list: []
}, action) {
    switch (action.type) {
        case "init": // 初始化fruits
            return { state, list: action.payload }
        case "add": // 新增
            return { ...state, list: [...state.list, action.payload] };
        default:
            return state;
    }
}

把combineReducers引入,并作为createStore的第一个参数

代码语言:javascript
复制
// store/index.js
import { combineReducers } from "redux";
import fruitReducer from './fruitReducer';
const store = createStore(
  // 此处设置别名`fruit`
  combineReducers({ fruit: fruitReducer }),
  applyMiddleware(logger, thunk)
);

这时候,在组件Hook.js中,可以以 state.fruit调用状态:

代码语言:javascript
复制
// ReduxTest.js
import {addFruit,asyncFetch} from "../store/fruitReducer";
const mapStateToProps = state => ({
  fruits: state.fruit.list
});
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-06-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 一Li小麦 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 使用redux
    • 创建store实例:createStore
      • 注册provider
        • 重构
          • 异步处理
            • 模块化(combineReducers)
            相关产品与服务
            消息队列 TDMQ
            消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档