Action
Action 是把資料從應用(伺服器響應,使用者輸入或其它非 view 的資料 )傳到 store 的有效載荷。它是 store 資料的唯一來源。一般來說你會通過 store.dispatch() 將 action 傳到 store。分下邊兩類.
/*
* action 常量
*/
export const ADD_TODO = 'ADD_TODO';
export const VisibilityFilters = {
SHOW_ALL: 'SHOW_ALL',
SHOW_COMPLETED: 'SHOW_COMPLETED',
}
/*
* action 建立函式
*/
export function addTodo(text) {
return { type: ADD_TODO, text }
}
export const addTodo = (id)=>{
return {
type: EDITORUSERID,
id:id
}
}
複製程式碼
reducer
是一個純函式,接收舊的 state 和 action,返回新的 state。
(previousState, action) => newState
注意:永遠不要在 reducer 裡做這些操作:
- 修改傳入引數;
- 執行有副作用的操作,如 API 請求和路由跳轉;
- 呼叫非純函式,如 Date.now() 或 Math.random()。
function todos(state = [], action) {
switch (action.type) {
case ADD_TODO:
return [
...state,
{
text: action.text,
completed: false
}
]
default:
return state
}
}
複製程式碼
combineReducers管理多個Reducer
const todoApp = combineReducers({
visibilityFilter,
todos
})
export default todoApp
當你觸發 action 後,combineReducers 返回的 todoApp 會負責呼叫兩個 reducer:
let nextTodos = todos(state.todos, action)
注意:也可以 reducer 放到一個獨立的檔案中,通過 export 暴露出每個 reducer 函式import * as reducers from './reducers'
複製程式碼
Store
Store 有以下職責:
- 維持應用的 state;
- 提供 getState() 方法獲取 state;
- 提供 dispatch(action) 方法更新 state;
- 通過 subscribe(listener) 註冊監聽器;
- 通過 subscribe(listener) 返回的函式登出監聽器
import todoApp from './reducers'
let store = createStore(todoApp)
複製程式碼
createStore() 的第二個引數是可選的, 用於設定 state 初始狀態。這對開發同構應用時非常有用,伺服器端 redux 應用的 state 結構可以與客戶端保持一致, 那麼客戶端可以將從網路接收到的服務端 state 直接用於本地資料初始化。
let store = createStore(todoApp, window.STATE_FROM_SERVER)
容器元件
Redux 的 React 繫結庫是基於 容器元件和zhan shi元件相分離 的開發思想
展示元件 | 容器元件 | |
---|---|---|
作用 | 描述如何展現(骨架、樣式) | 描述如何執行(資料獲取、狀態更新) |
直接使用 Redux | 否 | 是 |
資料來源 | props | 監聽 Redux state |
資料修改 | 從 props 呼叫回撥函式 | 向 Redux 派發 actions |
呼叫方式 | 手動 | 通常由 React Redux 生成 |
展示元件就是一般的js檔案容器元件往往使用connect(mapStateToProps,mapDispatchToProps)
建立。
mapStateToProps是把容器元件state向展示元件props對映。mapDispatchToProps() 是對映回撥方法。例如,我們希望 VisibleTodoList 向 TodoList 元件中注入一個叫 mOnClick 的 props ,還希望 onTodoClick 能分發 increaseAction 這個 action:
const App=connect(
(state)=>({
value:state.count
}),(dispatch)=>({
mOnClick:()=>dispatch(increaseAction)
})
)(Counter);
複製程式碼
Provider
所有容器元件都可以訪問 Redux store,建議的方式是使用指定的 React Redux 元件 來包裹,讓所有容器元件都可以訪問 store,
let store = createStore(todoApp)
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
複製程式碼
//建立元件的簡單寫法
const App = () => (
<div>
<AddTodo />
<VisibleTodoList />
<Footer />
</div>
)
export default App
複製程式碼
高階部分(處理非同步Action)
標準的做法是使用 Redux Thunk
中介軟體。
action 建立函式除了返回 action 物件外還可以返回函式。這時,這個 action 建立函式就成為了 thunk。這個函式會被 Redux Thunk middleware 執行。
我們仍可以在 actions.js 裡定義這些特殊的 thunk action 建立函式。
建立thunk action
//thunk action
// 雖然內部操作不同,你可以像其它 action 建立函式 一樣使用它:
// store.dispatch(fetchPosts('reactjs'))
export function fetchPosts(subreddit) {
return function (dispatch) {
// 首次 dispatch:更新應用的 state 來通知
// API 請求發起了。
dispatch(requestPosts(subreddit))
return fetch(`http://www.subreddit.com/r/${subreddit}.json`)
.then(
response => response.json(),
error => console.log('An error occurred.', error)
)
.then(json =>
dispatch(receivePosts(subreddit, json))
)
}
}
export function fetchPostsIfNeeded(subreddit) {
// 當快取的值是可用時,
// 減少網路請求很有用。
return (dispatch, getState) => {
if (shouldFetchPosts(getState(), subreddit)) {
// 在 thunk 裡 dispatch 另一個 thunk!
return dispatch(fetchPosts(subreddit))
} else {
// 告訴呼叫程式碼不需要再等待。
return Promise.resolve()
}
}
}
複製程式碼
middleware
你可以利用 Redux middleware 來進行日誌記錄、建立崩潰報告、呼叫非同步介面或者路由等等。應用中介軟體要改造下createStore()
* 記錄所有被髮起的 action 以及產生的新的 state。
*/
const logger = store => next => action => {
console.group(action.type)
let result = next(action)
console.log('next state', store.getState())
return result
}
let store = createStore(
todoApp,
applyMiddleware(
logger
)
複製程式碼
優化減少模版程式碼
1 action優化 1.1 你可以寫一個用於生成 action creator 的函式:
function makeActionCreator(type, ...argNames) {
return function(...args) {
let action = { type }
argNames.forEach((arg, index) => {
action[argNames[index]] = args[index]
})
return action
}
}
const ADD_TODO = 'ADD_TODO'
const EDIT_TODO = 'EDIT_TODO'
export const addTodo = makeActionCreator(ADD_TODO, 'todo')
export const editTodo = makeActionCreator(EDIT_TODO, 'id', 'todo')
複製程式碼
1.2非同步 Action Creators
export function loadPosts(userId) {
return {
// 要在之前和之後傳送的 action types
types: ['LOAD_POSTS_REQUEST', 'LOAD_POSTS_SUCCESS', 'LOAD_POSTS_FAILURE'],
// 檢查快取 (可選):
shouldCallAPI: (state) => !state.users[userId],
// 進行取:
callAPI: () => fetch(`http://myapi.com/users/${userId}/posts`),
// 在 actions 的開始和結束注入的引數
payload: { userId }
};
}
複製程式碼
2.reducer重構
方法抽取
function addTodo(state, action) {
...
return updateObject(state, {todos : newTodos});
}
function todoReducer(state = initialState, action) {
switch(action.type) {
case 'SET_VISIBILITY_FILTER' : return setVisibilityFilter(state, action);
case 'ADD_TODO' : return addTodo(state, action);
default : return state;
}
}
複製程式碼
善用combineReducers
函式
// 使用 ES6 的物件字面量簡寫方式定義物件結構
const rootReducer = combineReducers({
todoReducer,
firstNamedReducer
});
const store = createStore(rootReducer);
複製程式碼
3.大多數應用會處理多種資料型別,通常可以分為以下三類:
- 域資料(Domain data): 應用需要展示、使用或者修改的資料(比如 從伺服器檢索到的所有 todos
- 應用狀態(App state): 特定於應用某個行為的資料(比如 “Todo #5 是現在選擇的狀態”,或者 “正在進行一個獲取 Todos 的請求”)
- UI 狀態(UI state): 控制 UI 如何展示的資料(比如 “編寫 TODO 模型的彈窗現在是展開的”)
一個典型的應用 state 大致會長這樣:
{
domainData1 : {},
domainData2 : {},
appState1 : {},
appState2 : {},
ui : {
uiState1 : {},
uiState2 : {},
}
}
複製程式碼
必要時可採用 Redux-ORM