从零搭建React+TypeScript的后台项目,三

本章主要讲解redux在React项目中的应用。Redux官方文档

一、基本概念

Action

action简单理解就是对象字面量。功能上来说就是把数据从应用传到store的有效载荷,和Vue中Mutation提交载荷类似。

有action创造函数,是一个生成action的方法。下面就是一个简单的同步actin:

export function setGlobalState(data: {}) {
    return {type: 'SET_GLOBAL_STATE', data}
}

action生成后,store并没有生成或者发生变化,只是说明我要更新store了。怎么更新需要通过reducer函数来处理。

Reducer

reducer本质上是纯函数,接受oldState、action两个参数,返回newState。reducer告诉开发系统是怎么更新store的。

function global(state: Global, action: any) {
    switch(action.type) {
        case 'SET_GLOBAL_STATE':
            return action.data
        default:
            return InitGlobalState
    }
}

reducer支持组合和拆分,可以根据项目业务合理进行组织。

Store

store就是项目中的数据流,将action和reducer联系起来。Redux应用只有一个store,起职责如下:

  • 维持应用的 state;
  • 提供 getState() 方法获取 state;
  • 提供 dispatch(action) 方法更新 state;
  • 通过 subscribe(listener) 注册监听器;
  • 通过 subscribe(listener) 返回的函数注销监听器。

下面我们初始化一个store

import { createStore } from 'redux'

// 初始化store
const Store = createStore(
  rootReducer,
)

二、React中使用Redux

redux是一种状态管理通用实现方案,可以应用在许多地方,并不受库的限制。react-redux正是两者搭配起来实现的通用库。

yarn add react-redux

Provider全局注入store

然后通过react-redux提供的Provider组件实现store注入。

import Store from './store'
import { Provider } from 'react-redux'

ReactDOM.render(
    <Provider store={Store}>
        <App/>
    </Provider>,
    document.getElementById('root') as HTMLElement  //类型断言
);

这样项目中的所有组件都能访问到store中state,并且通过dispatch来更新state。总之,store在项目中是单独存在的一个整体,作为数据源存在。而项目外部可以获取state,改变store中state的唯一方法就是dispatch(action)。

组件中使用store

下面我们一todo.tsx组件为例,介绍组件中是如何获取、更新state的。

import { connect } from 'react-redux'

const mapStateToProps = (state:InitState) => {
    return {
        todos: state.todos
    }
}
  
const mapDispatchToProps = (dispatch:Dis) => {
    return {
        onAddTodo(value: string) {
            dispatch(addTodo(value))
      }
    }
}

Provider组件将store注入到组件中的本质,还是将store通过组件的props关联到每个组件上。接下来我们将两个map函数绑定到组件的属性上。

interface Props {
    todos: []
    onAddTodo:(value: string) => void 
}
interface State {
    value: string
}

class Todo extends React.Component<Props, State> {    

    constructor(props: Props) {
        super(props)
        this.state = {
            value: ''
        }
    }

    handleAddTodo = () => {
        this.props.onAddTodo(this.state.value)
        this.setState({
            value: ''
        })
    }

    handleInputChange = (e: any) => {
        const { value } = e.target
        this.setState({
            value
        })
    }

    render () {
        return(
            <div>
                <Input value={this.state.value} placeholder="请输入清单项" onChange={this.handleInputChange}/>
                <Button onClick={this.handleAddTodo}>添加</Button>
                <ul>
                    {this.props.todos.map((todo: any) => (
                        <li>{todo.text}</li>
                    ))}
                </ul>
            </div>
        )
    }
}

export default connect(
    mapStateToProps,
    mapDispatchToProps
)(Todo)

这样我们就实现了在组件内部获取state,并且通过界面交互可以动态新增待办清单,更新store,最后实时渲染页面的功能。

三、redux高级用法

组合拆分reducer

之前有讲到可以拆分reducer,做到单一功能模块对应一个reducer。类似于Vuex中将store分成多个子modules。

redux提供的combineReducers方法即可接受多个reducer。

import { createStore, combineReducers } from 'redux'
import Todo from './todo/reducers'
import Ware from './ware/reducers'

const rootReducer = combineReducers(
    Object.assign({}, Todo, Ware)
)

然后将rootReducer传入createStore中即可。

action中间件

中间件MiddleWare可以在我们进行disatch时,更加细粒化的跟踪store的变化,并且允许我们建立异步action。

下面我们来看react-thunk中间件的作用,使用middleWare后,action创建函数除了返回action对象外,还能返回函数。这个函数会被react-thunk执行,在函数内部能执行更多操作,执行异步请求、dispatch action。

yarn add react-thunk

下面我们来修改下store/index.tsx文件:

import thunkMiddleware from 'redux-thunk'
import { createStore, applyMiddleware, combineReducers } from 'redux'

const Store = createStore(
  rootReducer,
  applyMiddleware(  
    thunkMiddleware,
  )
)

现在我们的action创造函数中就能像下面这样写:

export function addTodo(text: string) {
    return { type: 'ADD_TODO', text }
}

export function asyncAddTodo(text: string) {
  return (dispatch:Dis, getState: Get) => {
    if (checkStoreTodo(getState(),text)) {
      // 在 thunk 里 dispatch 另一个 thunk!
      return dispatch(addTodo(text))
    } else {
      // 告诉调用代码不需要再等待。
      return Promise.resolve()
    }
  }
}

还有react-logger提供的中间件,在本地开发时在进行dispatch时,能打印改动日志。不仅如此,我们还可以自定义middleWare,用来实现自己想要的逻辑效果。

现在redux在react中一些基本用法都介绍完毕了,react中的一些第三方库基本和TypeScript结合的很好,可能有时候回调函数的参数不知道如何进行类型定义。有个d.ts文件则能在开发时进行智能提示,这个文件主要是在ts文件以js文件发布后,用来标记js文件里面对应的类型。

TypeScript官方文档有兴趣的朋友可以看看。

题外话,redux使用起来有点复杂。安利另外一个状态管理方案MobX,并且在react得到很好的实现,真正做到了开箱即用,代码写到哪里就在哪里使用!

这是一个系列文章:

从零搭建React+TypeScript的后台项目(一)--构建基础React+TypeScript项目

从零搭建React+TypeScript的后台项目(二)--后台router实现方案

从零搭建React+TypeScript的后台项目(三)--Redux基本配置