Redux Actions & Redux Thunk 的思考与实践

[slide]

Redux Actions & Redux Thunk

[slide]

Agenda

  • FSA
  • Action Creator
  • redux-actions
  • redux-promise
  • redux-thunk

[slide]

Agenda

  • FSA
  • Action Creator
  • redux-actions
  • redux-promise
  • redux-thunk

[slide]

Rails REST Routes


HTTP Verb Path Controller#Action Used for
GET /todos todos#index display a list of all todos
GET /todos/new todos#new return an HTML form for creating a new todo
POST /todos todos#create create a new todo
GET /todos/:id todos#show display a specific todo
GET /todos/:id/edit todos#edit return an HTML form for editing a todo
PATCH/PUT /todos/:id todos#update update a specific todo
DELETE /todos/:id todos#destroy delete a specific todo

[note]
Restul中由Http Verb 和 Path 共同决定 “要做什么”
[/note]

[slide]

Rails Controller & Actions

class TodosController < ApplicationController
  # GET /todos
  def index
    @todos = Todo.all
  end
  
  # GET /todos/:id
  def show
    @todo = Todo.find(params[:id])
  end
  
  # GET /todos/new
  def new
    @todo = Todo.new
  end

  # POST /todos
  def create
    @todo = Todo.new(params[:todo])
 
    if @todo.save
      redirect_to @todo
    else
      render 'new'
    end  
  end
  
  # ...
end 

[note]
Controller 中的 Action 可以从 params中获取 http 参数。
结合起来看,Flux 中的 Action 要做的事情也极其类似:

  1. 决定“要做什么”
  2. 携带必要的参数
    [/note]

[slide]

Describe Redux Action in One Sentence

  • What to do ? {:&.fadeIn}
  • And along with necessary data

[slide]

FSA(Flux Standard Action)

{
  type: ADD_TODO,
  payload: {
    text: 'Do something.'  
  }
}
{
  type: ADD_TODO,
  payload: new Error(),
  error: true
}

[slide]

Flux Standard Action Specifications

  • An action MUST {:&.fadeIn}

    • be a plain JavaScript object. {:&.fadeIn}
    • have a type property.
  • An action MAY have:

    • an error property. {:&.fadeIn}
    • a payload property.
    • a meta property.
  • An action MUST NOT include properties other than type, payload, error, and meta.

[slide]

Benefit of using FSA

  • Standard to Follow {:&.fadeIn}
  • Easy to switch Flux implementations
  • More friendly to middleware

[slide]

Use Action Literal Directly

  • {:&.fadeIn}
store.dispatch({
  type: ADD_TODO,
  payload: {
    text: 'Do one thing.'  
  }
})
store.dispatch({
  type: ADD_TODO,
  payload: {
    text: 'Do another thing.'  
  }
})

[note]
直接使用 Action 字面量虽然直接,但繁琐、重复、极像样板(Boilerplate)、代码将代码的主要目的淹没在了细节中(暴露了不必要的细节)
[/note]

[slide]

Encapsulate Action in Action Creator

  • {:&.fadeIn}
const addTodo = (text) => {
  return {
    type: ADD_TODO,
    payload: { text }
  }  
}
store.dispatch(addTodo('Do one thing.'))
store.dispatch(addTodo('Do another thing.'))

[note]
利用 Action Creator 封装了细节,减少重复,且令调用代码的意图一目了然。
[/note]

[slide]

Todo Actions (by Action Creators)

  • {:&.fadeIn}
const addTodo = (text) => {
  return {
    type: ADD_TODO,
    payload: { id: uuid.v4(), text }
  }
}

const updateTodo = (id, text) => {
  return {
    type: UPDATE_TODO,
    payload: { id, text }
  }
}
const clearTodos = () => {
  return {type: CLEAR_TODOS}
}

[note]
即使采用了 Action Creator,但还是有重复的影子,不够完美。
[/note]

[slide]

Use redux-actions to Simplify Action Creator

  • {:&.fadeIn}
// Solution One, which is perfer
const addTodo = (text) => {
  return createAction(ADD_TODO)({ id: uuid.v4(), text })
}
store.dispatch(addTodo(1, 'Do something'))

// Solution Two
const addTodo = createAction(ADD_TODO)
store.dispatch(addTodo({id: uuid.v4(), text: 'Do something'}))
const updateTodo = (id, text) => {
  return createAction(UPDATE_TODO)({ id, text })
}
const clearTodos = createAction(CLEAR_TODOS_TODO)
store.dispatch(clearTodos())

[note]
借助 redux-actions 的 createAction 进一步消除重复,简化代码。
[/note]

[slide]

Common Misuse of redux-actions

  • {:&.fadeIn}
const deleteTodo = (id) => {
  return createAction(DELETE_TODO)(id)
}
// Same as
const deleteTodo = (id) => {
  return {
    type: DELETE_TODO,
    payload: id
  }
}
const todos = handleActions({
  [DELETE_TODO]: (state, { payload }) => {
    // What the hell is payload ?
    return deleteTodo(state, payload)
  },
}, [])

[note]
payload 中的数据应该有自己的结构,而非直接塞给 payload,否则的话,reducer 里代码根本不知道 payload 里有啥。
[/note]

[slide]

the Way to use redux-actions

  • {:&.fadeIn}
const deleteTodo = (id) => {
  return createAction('DELETE_TODO')({ id })
}
const todos = handleActions({
  DELETE_TODO: (state, { payload }) => {
    return deleteTodo(state, payload.id)
  },
}, [])

[slide]

use redux-promise to Call API

const fetchTodo = (id) => {
  return {
    type: 'FETCH_TODO',
    payload: fetch(`/todos/${id}`)
  }
}

// Or 
const fetchTodo = (id) => {
  return fetch(`/todos/${id}`)
           .then(todo => {
               return {
               type: 'FETCH_TODO',
               payload: todo
             }         
           })
}

store.dispatch(fetchTodo(1))

[note]
redux-promise 这个 middleweare 可以识别 payload 为 promise 的 action 或 全由 promise 组成的整个 action。
[/note]

[slide]

[magic data-transition="cover-circle"]

use redux-thunk to Call API

const fetchTodo = (id) => {
  return (dispatch, getState) => {
    fetch(`/todos/${id}`)
      .then(todo => {
        dispatch(createAction(FETCH_TODO)({ todo }))
      })
      .catch(error => {
        dispatch(createAction(FETCH_TODO)(error))
      })
  }
}

store.dispatch(fetchTodo(1))

[note]
redux-thunk 也可处理 promise。
[/note]

=====

use redux-thunk to Conditional Dispatch

const addTodo = (text) => {
  return (dispatch, getState) => {
    if (getState().todos.length <= 3) {
        dispatch(createAction(FETCH_TODO)({ todo }))
    }
  }
}

[note]
redux-thunk 还可用于按需调用。
[/note]

=====

Use redux-thunk to Dispatch Multiple Actions at Once

const fetchTodo = (id) => {
  return (dispatch, getState) => {
    dispatch(createAction(START_FETCH_TODO)({ todo }))
    
    fetch(`/todos/${id}`)
      .then(todo => {
        dispatch(createAction(FETCH_TODO_SUCCESS)({ todo }))
        // dispatch(createAction(TODO_RECIVED)({ todo }))
      })
      .catch(error => {
        dispatch(createAction(FETCH_TODO_FAIL)(error))
      })
  }
}

[note]
redux-thunk 还可用于批量调用多个 action。显然 redux-thunk 的适用场景要比 redux-promise 要广。
[/note]

[/magic]

[slide]

[magic data-transition="cover-circle"]

Overuse redux-thunk

const updateTodo = (id, text) => {
  return (dispatch) => {
     return dispatch(createAction(UPDATE_TODO)({ id, text }))
  }
}

[note]
并非所有的 action 都要写成 thunk 形式。
[/note]

====

Overuse redux-thunk

const mapStateToProps = (state, ownProps) => ({})
const mapDispatchToProps = (dispatch, ownProps) => ({
  updateTodo: (id, text) => {
    return (dispatch) => {
      return dispatch(createAction(UPDATE_TODO)({ id, text }))
    }
  },
  addTodo: (text) => {
    return (dispatch) => {
      return dispatch(createAction(ADD_TODO)({ id: uuid.v4(), text }))
    }
  },
  toggleTodo: (id) => {
    return (dispatch) => {
      return dispatch(createAction(TOGGLE_TODO)({ id: uuid.v4(), text }))
    }  
  },                
})

@connect(mapStateToProps, mapDispatchToProps)

[note]
把上面的方法 inline 后,就显得更碍眼了。
[/note]

====

Overuse reason ?

  • Not familiar with bindActionCreators
  • Suppose will take over everything
  • Copy & paste

====

react-redux will auto use bindActionCreators

const mapStateToProps = (state, ownProps) => ({})
const mapDispatchToProps = (dispatch, ownProps) => ({
  updateTodo: (id, text) => {
    return (dispatch) => {
      return dispatch(createAction(UPDATE_TODO)({ id, text }))
    }
  },
  addTodo: (text) => {
    return (dispatch) => {
      return dispatch(createAction(ADD_TODO)({ id: uuid.v4(), text }))
    }
  },
  toggleTodo: (id) => {
    return (dispatch) => {
      return dispatch(createAction(TOGGLE_TODO)({ id: uuid.v4(), text }))
    }  
  },                
})
const mapStateToProps = (state, ownProps) => ({})
const mapDispatchToProps = {
  updateTodo: (id, text) => createAction(UPDATE_TODO)({ id, text }),
  addTodo: (text) => createAction(ADD_TODO)({ id: uuid.v4(), text }),
  toggleTodo: (id) => createAction(TOGGLE_TODO)({ id: uuid.v4(), text }),                
}

// will auto call bindActionCreators(mapDispatchToProps, dispatch)

@connect(mapStateToProps, mapDispatchToProps)

====

redux-thunk Source Code

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

====

Fix The Overuse of redux-thunk

const updateTodo = (id, text) => {
  return (dispatch) => {
     return createAction('UPDATE_TODO')({ id, text })
  }
}
const updateTodo = (id, text) => createAction('UPDATE_TODO')({ id, text })

[/magic]

[slide]

Thank You & QA

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容