一 使用API
目前境况是:用户与APP交互,而action被dispatch以反映某些事件。数据直接存在于浏览器中,这意味着如果用户刷新,所有操作进展都会丢失。
目前为止dispatch的action都是同步的。当action被dispatch时,store会立即接收到它们。这种action简单直接。
但现实中,很难找到完全不涉及异步代码的JS APP。
1.1 异步action
在此之前所有的action都是同步的。当dispatch同步action时,没有任何空间处理额外的功能。
异步action提供了一种处理异步操作的方法,通常返回同步action以提供可读性
export function fetchTasks(){
return dispatch => {
api.fetchTasks().then(resp => {
dispatch(fetchTasksSucceeded(resp.data))
})
}
}
1.2 使用redux-thunk调用异步action
redux-thunk是一个redux中间件。(关于中间件详细后面会讲到)redux-thunk将允许像dispatch标准action对象一样dispatch函数,可安全地添加异步代码。
src/app.js
//...
componentDidMount(){
this.props.dispatch(fatchTasks())
// 请求初始数据
}
//...
src/actions/index.js
import axios from 'axios';
export function fetchTasks(){
return dispatch => {
axios.get('http://localhost:3001/tasks')
.then(resp => {
dispatch(fetchTasksSucceeded(resp.data));
})
}
}
export function fetchTasksSucceeded(tasks){
return {
type: FETCH_TASKS_SUCCEEDED,
paylpad: {
tasks
}
}
}
优化 API客户端
src/api/index.js
import axios = from 'axios';
const API_BASE_URL='http://localhost:3001';
export const client = axios.create({
baseURL: API_BASE_URL,
headers: {
'Content-type': 'application/json',
}
})
src/action/index.js
import * as api from '.../api';
export function fetchTasks(){
return dispatch => {
api.client().get('/tasks').then(resp=>{
dispatch(fetchTasksSucceeded(resp.data))
})
}
}
view action 和 server action
view action: 由用户发起 如:FETCH_TASKS / CREATE_TASK / EDIT_TASK serve action: 由服务器发起 如:FETCH_TASKS_SUCCEEDED
1.3 将任务保存到服务器
src/action/index.js
import * as api from '.../api';
function createTasksSucceeded(task){
return {
type: CREATE_TASK_SUCCESSDED,
payload: {
task
}
}
}
export function createTasks({title, description, status='Unstarted'}){
return dispatch => {
api.client().post('/tasks',{title, description, status}).then(resp=>{
dispatch(createTasksSucceeded(resp.data))
})
}
}
function editTaskSucceeded(task){
return {
type: EDIT_TASK_SUCCEEDED,
payload:{
task
}
}
}
export function editTask(id, params={}) {
return (dispatch, getState) => {
const task = getTaskById(getState().tasks, id);
const updatedTask = Object.assign({}, task, params)
api.client().post(id, updatedTask).then(resp=>{
dispatch(editTaskSucceeded(resp.data))
})
}
}
function getTaskById(tasks, id){
return tasks.find(task => task.id === id)
}
1.4 加载状态
当进入加载状态时,使用redux跟踪请求状态,并在请求进行过程中更新UI以展示合适的反馈
请求生命周期
对网络请求,需要关注两个时刻:何时开始以及何时完成。将成些事件建模为action,最终会得到3种action
- FETCH_TASKS_STARTED -- 请求发起时dispatch
- FETCH_TASKS_SUCCEEDED -- 请求成功完成时dispatch
- FETCH_TASKS_FAILED -- 请求失败时dispatch, payload通常来自服务器的错误信息
src/reducers/index.js
const initialState = {
tasks: [],
isLoading: false
}
export default function tasksReducer(sate = initialState, action) {
switch(action.type){
case FETCH_TASKS_STARTED: {
return {
...state,
isLoading: true,
}
}
case FETCH_TASKS_SUCCEEDED: {
return {
...state,
isLoading: false,
tasks: action.paylpad.tasks
}
}
default: {
return state;
}
}
}
随着添加更多的功能,需要在redux中跟踪更多的数据,可为store添加新的顶层属性
index.js
import {tasksReducer, projectsReducer} from './reducers';
const rootReducer = (state = {}, action) => {
return {
tasks: tasksReducer(state.tasks, action),
projects: projectsReducer(state.projects, action),
}
}
const store = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(thunk))
)
这样就允许每个reducer不用关心store的整体形状,而只关心它所操作的数据片段
二 中间件
中间件是什么?中间件是指两个软件组件之间运行的代码,一般作为框架的一部分。
redux中间件是位于action和store之间的代码。与服务器中间件有助于跨多个请求运行代码的方式类似,redux中间件允许跨多个action派发运行代码
2.1 中间件的基础知识
创建中间件包含两个简单的步骤:
- 用正确的函数签名定义中间件
- 使用redux的applyMiddleware函数将中间件与store一起注册
首先:中间件的函数签名
const middlewareExample = store => next => action => {...}
- store -- 需要基于现有状态做出决定时,可使store对象 / tore.getState方法满足所需
- next -- 将action传递给链中下一个中间件时调用的函数
- action -- 被派发的action。通常,中间件将对每个action执行一些操作、或通过检查action.type来监视特定的action
组合中间件
中间件一个重要方面在于它的链接能力,所有redux中间件都必须以相同的方式创建,所以将自己的中间件与第三方中间件结合起来使用是完全可行的。
中间件的真正好处是能够将需要跨多个action执行的一些任务集中化。
案例分析:如何不使用中间件
登录重定向
export function login(params){
return dispatch => {
api.client().post('/login', params).then(resp=>{
dispatch(loginSucceeded(resp.data))
// 执行重定向
dispatch(navigate('/dashboard'))
})
}
}
另一方面,还可在中间件中添加特定的路由逻辑
export function login(params){
return dispatch => {
api.client().post('/login', params).then(resp=>{
dispatch(loginSucceeded(resp.data))
})
}
}
// within a routing middleware file
const routing = store => next => action => {
if(action.type === LOGIN_SUCCEEDED){
storeeee.dispatch(navigate('/dashboard'))
}
}
这里关键是显式与隐式的区别,中间件能帮助减小重复和集中逻辑,但这是有代价的,取决于做出的最佳判断。中间件就是抽象,它的主要目的是帮助开发。
API中间件
回顾下服务器API调用的三大关键时刻:
- FETCH_TASKS_STARTED
- FETCH_TASKS_SUCCEEDED
- FETCH_TASKS_FAILED
对于中间件来说,所有进行API调用的action都需要三项内容:
- CALL_API字段,在中间件中定义和导出
- types属性,对应的数组由用于请求开始、成功、失败的3种action组成
- endpoint属性,用于明确需要请求的资源的相对URL
src/actions/index.js
import {CALL_API} from '../middleware/api';
export function fetchTasks(){
return {
[CALL_API]: {
types: [
FETCH_TASKS_STARTED,
FETCH_TASKS_SUCCEEDED,
FETCH_TASKS_FAILED
],
endpoint: '/tasks'
}
}
}
src/middle/api.js
export const CALL_API = 'CALL_API';
const apiMiddleware = stpre => next => action => {
const callApi = action[CALL_API];
if(typeof callApi === 'undefined'){
return next(action);
// 如果不是这个中间件关心的action,那么不进行任何处理并继续
}
}
src/index.js
import apiMiddleware from './middleware/api';
cosnt store = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(thunk, apiMiddleware))
)
应用中间件的顺序很重要。
现在是实现中间件主要代码时候了
src/middle/api.js
export const CALL_API = 'CALL_API';
const apiMiddleware = store => next => action => {
const callApi = action[CALL_API];
if(typeof callApi === 'undefined'){
return next(action);
}
const [requestStartedType, successType, failureType] = callApi.types;
// 使用数组解构方式创建每个action类型变量
next({type: requestStartedType})
// next最终会向store派发action,与store.dispatch相同。
}
接下来添加AJAX请求的函数 src/middle/api.js
import axios = from 'axios';
const API_BASE_URL='http://localhost:3001';
function makeCall(endpoint, method = 'GET', body){
const url = `${API_BASE_URL}${endpoint}`;
const params = {
method,
url,
data: body,
headers: {
'Content-Type': 'application/json',
}
}
return axios(params).then(resp => resp).catch(err => err)
}
const apiMiddleware = store => next => action => {
const callApi = action[CALL_API];
if(typeof callApi === 'undefined'){
return next(action);
}
const [requestStartedType, successType, failureType] = callApi.types;
next({type: requestStartedType})
return makeCall({
method: callApi.method,
body: callApi.body,
endpoint: callApi.endpoint,
}).then(
respinse =>
next({
type: successType,
paylpad: respinse.data,
}),
error =>
next({
type: failureType,
error: error.message,
})
)
}
这里主要好处是,如果需要增加额外的用于发起服务器请求的异步action,可极大地减少随之而来的重复工作。可使用API中间件来完成繁重的工作,而不是创建3种新的action类型并手动dispatch它们。
虽然添加了几行代码,但收益是巨大的。 src/actions/index.js
export function createTask({title, description, status = 'Unstarted'}){
return {
[CALL_API]: {
types: [
FETCH_TASKS_STARTED,
FETCH_TASKS_SUCCEEDED,
FETCH_TASKS_FAILED
],
endpoint: '/tasks',
method: 'POST',
body: {
title,
description,
status,
}
}
}
}
好极了!总的来说,你更倾向哪种风格?让每个action创建器使用redux-thunk显式地dispatch action? 或者喜欢更强大的API中间件?