社区的状态管理有多种方式,看团队和个人偏好。目前主流状态管理举例。
Redux idea来自于Flux
。依赖于通过集中和不可变的数据的存储的方式来同步数据,并且该数据的更新将触发我们的应用程序的重新渲染。 状态更新通过reducers
的函数的显示处理。 由于组织明确,通常更容易理解一个行为将如何影响你的程序的状态。
-MobX ,该库已经通过TypeScript
编写完成。依赖于reactive模式,其中状态包装成可观察的并通过属性传递。 通过简单地将状态标记为可观察来完成任何观察者的状态完全同步。
两者都有不同的优点和权衡。 一般来说,Redux往往会看到更广泛的使用。
$ yarn add redux react-redux @types/react-redux --dev
这里需要注意:
@types/redux
因为Redux的定义文件(.d.ts)已经和redux一起安装了。我们需要定义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(纯函数)。
/**
* @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。
import { combineReducers } from 'redux'; // 连接reducers
import { enthusiasm } from './demo';
const rootReducer= combineReducers({
demo: enthusiasm,
});
export default rootReducer;
在使用Redux时,我们经常会写入组件以及容器。
组件通常与数据无关,并且主要在演示层面上工作。
容器通常包装组件并为他们提供显示和修改状态所需的任何数据。
(1) 组件
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
是一个可选的数字(只要在名字后面加上一个?
)。还有两个可选的回调属性:onIncrement
和onDecrement
:
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;
}
}
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解决方案。前文代码中提示过。
$ yarn add redux-devtools-extension @types/redux-devtools-extension --dev
为了将这些全部整合到一起,我们需要创建一个store。并且和我们所有的reducer一起设置好:
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();
后续
直此,整合redux的demo完成,你可以尝试点击加减看效果。后续会增加router配置。