React And TypeScript(二:集成Redux)

前言

社区的状态管理有多种方式,看团队和个人偏好。目前主流状态管理举例。

  • Redux idea来自于Flux。依赖于通过集中和不可变的数据的存储的方式来同步数据,并且该数据的更新将触发我们的应用程序的重新渲染。 状态更新通过reducers的函数的显示处理。 由于组织明确,通常更容易理解一个行为将如何影响你的程序的状态。

    -MobX ,该库已经通过TypeScript编写完成。依赖于reactive模式,其中状态包装成可观察的并通过属性传递。 通过简单地将状态标记为可观察来完成任何观察者的状态完全同步。

两者都有不同的优点和权衡。 一般来说,Redux往往会看到更广泛的使用。

(一) 安装Redux

$ yarn add redux react-redux @types/react-redux --dev

这里需要注意:

  1. 安装依赖需要加 types 才能得到定义文件(.d.ts)
  2. 不需要安装@types/redux因为Redux的定义文件(.d.ts)已经和redux一起安装了。

(二) 定义APP状态

我们需要定义Redux存储的状态。为此,我们需要创建一个名为src/entity/index.tsx的文件,该文件将包含整个程序中可能使用的类型的定义(PS:即数据模型)。

/**
 * @component entity
 * @description 实体数据模型
 * @time 2018/1/9
 * @author ***
 **/
 // 入门demo数据模型
export interface demo { 
    languageName: string;
    enthusiasmLevel: number;
}
// 全局state
export interface StoreState {
    demo: demo;
}

我们的意图是languageName将是此应用程序编写的编程语言(即TypeScript或JavaScript),而enthusiasmLevel会有所不同。
当我们写第一个容器时,我们会明白为什么为何故意使我们的状态与我们的属性略有不同。

(三) 定义行为

(1)创建行为常量 constants
创建`src/constants/demo.tsx“文件,并添加一组让我们的APP能够响应的消息类型。

/**
 * @component constants
 * @description 行为常量
 * @time 2018/1/9
 * @author ***
 **/
export const INCREMENT_ENTHUSIASM = 'INCREMENT_ENTHUSIASM';
export type INCREMENT_ENTHUSIASM = typeof INCREMENT_ENTHUSIASM;

export const DECREMENT_ENTHUSIASM = 'DECREMENT_ENTHUSIASM';
export type DECREMENT_ENTHUSIASM = typeof DECREMENT_ENTHUSIASM;

(2)创建行为和函数 actions

这个const/type模式允许我们一种可访问和可重构的方式使用TypeScript的字符串文本类型。然后,我们在需要创建src/actions/demo.tsx 文件并添加一系列行为和函数。

/**
 * @component actions
 * @description demo动作和函数
 * @time 2018/1/9
 * @author ***
 **/

import * as constants from '../constants/demo'

export interface IncrementEnthusiasm {
    type: constants.INCREMENT_ENTHUSIASM;
}

export interface DecrementEnthusiasm {
    type: constants.DECREMENT_ENTHUSIASM;
}

export type All= IncrementEnthusiasm | DecrementEnthusiasm;

export function incrementEnthusiasm(): IncrementEnthusiasm {
    return {
        type: constants.INCREMENT_ENTHUSIASM
    }
}

export function decrementEnthusiasm(): DecrementEnthusiasm {
    return {
        type: constants.DECREMENT_ENTHUSIASM
    }
}

我们创建了两种类型,描述增量动作和减量动作应该是什么样的。 我们还创建了一个类型(All)来描述存放增量或减量的地方。 最后,我们做了两个功能,实际上制造了我们可以使用的动作,而不是写出庞大的对象文字。

这里有明显的实例,当你在遇到事情的时候,可以随时查看像redux-actions这样的类库。

(3)创建高阶函数 reducer

写下我们的第一个reducer。
reducer只是通过创建我们应用程序状态的修改副本而产生更改的函数,但没有任何副作用。换句话说,它们就是我们所说的pure function(纯函数)。

  • (1)创建demo reduces
/**
 * @component reducers
 * @description demo高阶函数
 * @time 2018/1/9
 * @author ***
 **/
import { All } from '../actions/demo';
import { demo } from '../entity';
import * as Constants from '../constants/demo';
// 初始化
const initState= {
    enthusiasmLevel: 1,
    languageName: 'TypeScript',
}; 
export function enthusiasm(state: demo= initState, action: All): demo {
    switch (action.type) {
        case Constants.INCREMENT_ENTHUSIASM:
            return {...state,
                enthusiasmLevel: state.enthusiasmLevel + 1
            };
        case Constants.DECREMENT_ENTHUSIASM:
            return {...state,
                enthusiasmLevel: Math.max(1, state.enthusiasmLevel - 1)
            };
    }
    return state;
}

其功能是确保增量使enthusiasm level增加1,减量则是其减少1,但值不低于1。

  • (2)创建根reducers
import { combineReducers } from 'redux'; // 连接reducers
import { enthusiasm } from './demo';

const rootReducer= combineReducers({
    demo: enthusiasm,
});

export default rootReducer;

(四) 增加容器和组件

在使用Redux时,我们经常会写入组件以及容器。
组件通常与数据无关,并且主要在演示层面上工作。
容器通常包装组件并为他们提供显示和修改状态所需的任何数据。

(1) 组件

  • 创建demo组件 components/Hello/index.tsx
/**
 * @component Hello
 * @description demo组件
 * @time 2018/1/9
 * @author ***
 **/
import * as React from 'react';
import './index.less'
import { Button } from 'antd';

export interface Props {
    name: string;
    enthusiasmLevel?: number;
    onIncrement?: () => void;
    onDecrement?: () => void;
}

const getExclamationMarks=(numChars: number)=> {
    return numChars + 1;
};

/*** 暂时注释stateless组件写法,因为ts容器这边有个bug
const HelloComponent= ({ name, enthusiasmLevel= 1, onIncrement, onDecrement }: Props)=> {
    if (enthusiasmLevel <= 0) {
        throw new Error('You could be a little more enthusiastic. :D');
    }

    return (
        
Hello {name + getExclamationMarks(enthusiasmLevel)}
); }; export default HelloComponent;*/
export default class HelloComponent extends React.Component<Props, {}> { constructor(props: Props){ super(props); } render(){ const { name, enthusiasmLevel= 1, onIncrement, onDecrement }= this.props; if (enthusiasmLevel <= 0) { throw new Error('You could be a little more enthusiastic. :D'); } return (
"hello">
"greeting"> Hello {name + getExclamationMarks(enthusiasmLevel)}
); } }

我们从Props来获取我们组件所需要的属性。name是一个必要的字符串,enthusiasmLevel是一个可选的数字(只要在名字后面加上一个?)。还有两个可选的回调属性:onIncrementonDecrement

  • 创建demo样式 components/Hello/index.less
.hello {
  text-align: center;
  margin: 20px;
  font-size: 48px;
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;

  button {
    margin-left: 25px;
    margin-right: 25px;
    font-size: 18px;
    font-weight: 700;
    min-width: 50px;
  }
}
  • 创建打包index.tsx components/index.tsx
/**
 * @component components
 * @description 组件打包
 * @time 2018/1/9
 * @author ***
 **/
import HelloComponent from './Hello';

export {
    HelloComponent,
}

这个index.tsx将会将组件内部全部打包,方便外面容器导入,不用深层次去寻找目录。

(2) 容器

现在我们的组件已写好,需要将其包装到一个容器中。

我们创建一个名为src/views/Hello/index.tsx(目录社区一般是containers)的文件,并添加以下代码。

import { HelloComponent } from '../../components'; // 直接导入components就可解析
import * as Actions from '../../actions/demo';
import { StoreState } from '../../entity';
import { connect, Dispatch } from 'react-redux';

/***
 * 这里最关键的两块是原始的```Hello```组件和react-redux的```connect```函数。
 * ```connect```可以使用以下两个函数实际地将原始的```Hello```组件转化为一个容器:
 *
 * ```mapStateToProps```,可以将当前store的数据作为消息传给我们组件需要shape。
 * ```mapDispatchToProps```,可以创建回调属性来使用```dispatch```函数将行为推送到我们的store。
* */

export function mapStateToProps( { demo: { enthusiasmLevel, languageName: name } }: StoreState) {
    return {
        enthusiasmLevel,
        name,
    }
}

/*** 请注意,```mapStateToProps```仅创建```Hello```组件期望的属性中的2个。
 * 也就是说,我们仍然希望通过```onIncrement```和```onDecrement```回调。
 * ```mapDispatchToProps```是一个采用调度程序功能的函数。
 * 此调度程序功能可以将操作传递到我们的存储中进行更新,
 * 因此我们可以创建一对可以根据需要调用调度程序的回调函数。
* */

export function mapDispatchToProps(dispatch: Dispatch) {
    return {
        onIncrement: () => dispatch(Actions.incrementEnthusiasm()),
        onDecrement: () => dispatch(Actions.decrementEnthusiasm()),
    }
}

export function mergeProps(stateProps: any, dispatchProps: any, ownProps: any) {
    return { ...ownProps, ...stateProps, ...dispatchProps};
}

export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(HelloComponent);

上面代码中的mergeProps是因为组件不是 stateless写法 出现bug解决方案。前文代码中提示过。

(五) 增加store

  • (1)安装Redux可视化工具
$ yarn add redux-devtools-extension @types/redux-devtools-extension --dev

为了将这些全部整合到一起,我们需要创建一个store。并且和我们所有的reducer一起设置好:

  • (2)创建根store配置 src/store/index.tsx
/**
 * @component store
 * @description Store配置
 * @time 2018/1/9
 * @author ***
 **/
import { createStore } from 'redux';
import rootReducer  from '../reducers';

// 安装redux-devtools-extension的可视化工具。需去谷歌商店安装Redux-DevTools
import { composeWithDevTools } from 'redux-devtools-extension';

const StoreConfig=() => {
    return createStore(
        rootReducer,
        composeWithDevTools()
    );
}

export default StoreConfig;

(六) 修改入口文件

我们需要将整合redux的demo增加到 src/index.tsx

import * as React from 'react';
import * as ReactDOM from 'react-dom';
import registerServiceWorker from './registerServiceWorker';
import { Provider } from 'react-redux';
import Hello from './views/Hello';
import StoreConfig  from './store';
import './index.less';

const store= StoreConfig();

ReactDOM.render(
    
        
    ,
    document.getElementById('root') as HTMLElement
);
registerServiceWorker();

运行效果如下图
React And TypeScript(二:集成Redux)_第1张图片

后续

直此,整合redux的demo完成,你可以尝试点击加减看效果。后续会增加router配置。

你可能感兴趣的:(React)