原文地址: http://qinfanpeng.github.io/jekyll/update/2017/01/03/redux_actions_and_redux_thunk.html
[slide]
[slide]
[slide]
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]
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 要做的事情也极其类似:
[slide]
[slide]
{
type: ADD_TODO,
payload: {
text: 'Do something.'
}
}
{
type: ADD_TODO,
payload: new Error(),
error: true
}
[slide]
An action MUST {:&.fadeIn}
type
property.An action MAY have:
error
property. {:&.fadeIn}payload
property.meta
property.An action MUST NOT include properties other than type
, payload
, error
, and meta
.
[slide]
[slide]
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]
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]
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]
// 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]
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]
const deleteTodo = (id) => {
return createAction('DELETE_TODO')({ id })
}
const todos = handleActions({
DELETE_TODO: (state, { payload }) => {
return deleteTodo(state, payload.id)
},
}, [])
[slide]
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"]
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]
=====
const addTodo = (text) => {
return (dispatch, getState) => {
if (getState().todos.length <= 3) {
dispatch(createAction(FETCH_TODO)({ todo }))
}
}
}
[note] redux-thunk 还可用于按需调用。 [/note]
=====
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"]
const updateTodo = (id, text) => {
return (dispatch) => {
return dispatch(createAction(UPDATE_TODO)({ id, text }))
}
}
[note] 并非所有的 action 都要写成 thunk 形式。 [/note]
====
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]
====
bindActionCreators
====
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)
====
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;
====
const updateTodo = (id, text) => {
return (dispatch) => {
return createAction('UPDATE_TODO')({ id, text })
}
}
const updateTodo = (id, text) => createAction('UPDATE_TODO')({ id, text })
[/magic]
[slide]