1. 唯一数据源2. 保持状态只读3. 数据改变只能通过纯函数完成
在 Redux 框架下,一个 React 组件基本上就是要完成以下两个功能:
我们将一个任务拆分成两个组件,然后两个组件嵌套起来,完成原本一个组件完成的所有任务。
MVC 中,应用代码分为 Controller Model View ,分别代表 种模块角色,就是把所有的 Con oller 代码放在 controllers 目录下,把所有的 Model 代码放在 models 录下,把 View 代码放在 views 目录下 这种组织代码的方式,叫做“按角色组织”
把完成同一应用功能的代码放在一个目录下,一个应用功能包含多个角色的代码。在 Redux 中,不同的角色就是 reducer、actions 和视图,而应用功能对应的就是用户界面上的交互模块。
todoList/actons.jsactionTypes.jSindex.jsreducer.jsviews/cornponent.jscontainer.jsfilter/actions.jsactionTypes.jsindex. jsreducer.jsviews/cornponent.jscontainer.js
我下面我们以功能模块 todoList 和 filter,为例。当我们在 filter 模块想要使用 todoList 的 action 构造函数和视图该如何导入呢? bad
import * as actions from '../todoList/actions';import container as TodoList from './todoList/views/container';
因为 filter 模块依赖 todoList 模块的内部结构。 Good 在 todoList/index.js 中,代码如下:
import * as actions from './action.js'import reducer from './reducer.js'import view from './views/container.js'export {actions,reducer,view}//下面在filter模块导入todoList的代码import {actions,reducer,view as TodoList} from '../todoList';
一个模块控制一个状态节点 Store 上的每个 state 只能通过 reducer 来更改,而每个 reducer 只能更改 redux 状态树上一个节点下的数据。
react 在渲染数据时采用的"浅层比较"的方式只看这两个 prop 是不是同一个对象的引用。
Bad
<Todo style={{color:'red'}}/>
Good
//确保初始化只执行一次,不要放到render中const fooStyle={color:'red'};<Foo style={fooStyle}/>
Bad
<TodoItemkey={item.id}text={item.text}completed={item.completed}onToggle={()=>{ onToggleTodo(item.id)}}onRemove={()=>onRemoveTodo(item.id)}/>
上面的做法中,我们将回调函数传递给子组件的 onClick 事件,redux 认为每次都是一个新的函数,所以,我们可以将要传递的参数以属性传递给组件,让子组件去传递函数(mapDispathToProps 中)
<TodoItemkey={item.id}id={item.id}text={item.text}completed={item.completed}/>const mapDispatchToProps = (dispath: any, ownProps: any) => {const { id } = ownProps;return {onToggle: () => { console.log('onToggle'); dispath(toggleTodo(id)) },onRemove: () => dispath(removeTodo(id))}}//木偶组件应该用connect包起来,让redux 判读props是否改变export default connect(null,mapDispatchToProps)(TodoItem);
优点:
缺点:
改进: 我们可以使用 Redux 做状态管理来访问服务器。
对于访问服务器这样的异步操作,从发起操作到操作结束,都会有段时间延迟,在这段延迟时间中,用户可能希望中止异步操作。访问服务器这样的输入输出操作,复杂就复杂在返回的结果和时间都是不可靠的,即使是访问同样一个服务器,也完全可能先发出的请求后收到结果。
案例场景: 如果一个用户从一个请求发出到获得响应的过程中,用户等不及,或者改变主意想要执行另一个操作,用户就会进行一些操作发送新的请求发往服务器。
解决方案:
缺点: 用户体验不是很好,因为服务器响应是不可控的,锁的时间由服务器响应时间来决定,这不是一个最好方案。 2. 我们选择用户最后一次提交请求,其它请求我们将丢弃。
下面我们对一个天气请求函数分析(代码采用 tyescript 如果不用则将类型定义去掉就可以了): 不加中断:
export const fetchWeather = (cityCode: string) => {return (dispatch: any) => {const apiUrl = `/data/cityinfo/${cityCode}.html`;dispatch(fetchWeatherStarted());fetch(apiUrl).then((response) => {if (response.status !== 200) {throw new Error('Fail to get response will start' + response.status);}response.json().then(({ weatherinfo }) => {dispatch(fetchWeatherSuccess(weatherinfo));}).catch((error) => {throw new Error('Invaild json response' + error);});}).catch((error) => {dispatch(fetchWeatherFilure(error));});};};
添加中断:
let nextSeqId = 0;export const fetchWeather = (cityCode: string) => {return (dispatch: any) => {console.log('function');const apiUrl = `/data/cityinfo/${cityCode}.html`;const seqId = ++nextSeqId;const dispatchIfVaild = (action: any) => {if (seqId == nextSeqId) {console.log('请求开始');return dispatch(action);}};dispatchIfVaild(fetchWeatherStarted());fetch(apiUrl).then((response) => {if (response.status !== 200) {throw new Error('Fail to get response will start' + response.status);}response.json().then(({ weatherinfo }) => {dispatch(fetchWeatherSuccess(weatherinfo));}).catch((error) => {throw new Error('Invaild json response' + error);});}).catch((error) => {dispatch(fetchWeatherFilure(error));}).finally(() => {console.log('数据响应回来');});};};
我们通过 create-reat-app 构建的项目后,就自带了 Jest 库
npm run test
Jest 很智能,只运行修改代码影响的单元测试,同时我们还可以选择只运行满足过滤条件的单元测试用例等功能。
一. 方式一 我们在项目根目录创建一个名==test 目录==,和项目的 src 目录并行,在 test 目录建立和 src 对应子目录结构,每个单元测试的文件都以test.js 二. 我们可以在每个目录创建test子目录,用于存放对应这个目录的单元测试。
在 Jest 框架下,每个测试用例用一个 it 函数代码,it 函数第一个参数是一个字符串,代表的就是测试用例名称,第二个参数是一个函数,包含的就是实际的测试用例过程。
it('should return string when invoked', () => {});
我们有时对一个被测对象创建多个测试用例,此时对于如何组成多个测试用例(it),此时我们就需要测试套件。
describe('func',()=>{it('should return string when invoked',()={})})