<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                # 縮減樣板代碼 Redux 很大部分 [受到 Flux 的啟發](../introduction/PriorArt.md),而最常見的關于 Flux 的抱怨是必須寫一大堆的樣板代碼。在這章中,我們將考慮 Redux 如何根據個人風格,團隊偏好,長期可維護性等自由決定代碼的繁復程度。 ## Actions Actions 是用來描述在 app 中發生了什么的普通對象,并且是描述突變數據意圖的唯一途徑。很重要的一點是 **不得不 dispatch 的 action 對象并非是一個樣板代碼,而是 Redux 的一個 [基本設計選擇](../introduction/ThreePrinciples.md)**. 不少框架聲稱自己和 Flux 很像,只不過缺少了 action 對象的概念。在可預測性方面,這是從 Flux 或 Redux 的倒退。如果沒有可序列化的普通對象 action,便無法記錄或重演用戶會話,也無法實現 [帶有時間旅行的熱重載](https://www.youtube.com/watch?v=xsSnOQynTHs)。如果你更喜歡直接修改數據,那你并不需要使用 Redux 。 Action 一般長這樣: ```javascript { type: 'ADD_TODO', text: 'Use Redux' } { type: 'REMOVE_TODO', id: 42 } { type: 'LOAD_ARTICLE', response: { ... } } ``` 一個約定俗成的做法是,action 擁有一個不變的 type 幫助 reducer (或 Flux 中的 Stores ) 識別它們。我們建議你使用 string 而不是 [符號(Symbols)](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Symbol) 作為 action type ,因為 string 是可序列化的,并且使用符號會使記錄和重演變得困難。 在 Flux 中,傳統的想法是將每個 action type 定義為 string 常量: ```javascript const ADD_TODO = 'ADD_TODO' const REMOVE_TODO = 'REMOVE_TODO' const LOAD_ARTICLE = 'LOAD_ARTICLE' ``` 這么做的優勢是什么?**人們通常聲稱常量不是必要的。對于小項目也許正確。** 對于大的項目,將 action types 定義為常量有如下好處: - 幫助維護命名一致性,因為所有的 action type 匯總在同一位置。 - 有時,在開發一個新功能之前你想看到所有現存的 actions 。而你的團隊里可能已經有人添加了你所需要的 action,而你并不知道。 - Action types 列表在 Pull Request 中能查到所有添加,刪除,修改的記錄。這能幫助團隊中的所有人及時追蹤新功能的范圍與實現。 - 如果你在 import 一個 Action 常量的時候拼寫錯了,你會得到 `undefined` 。在 dispatch 這個 action 的時候,Redux 會立即拋出這個錯誤,你也會馬上發現錯誤。 你的項目約定取決與你自己。開始時,可能在剛開始用內聯字符串(inline string),之后轉為常量,也許再之后將他們歸為一個獨立文件。Redux 在這里沒有任何建議,選擇你自己最喜歡的。 ## Action Creators 另一個約定俗成的做法是通過創建函數生成 action 對象,而不是在你 dispatch 的時候內聯生成它們。 例如,不是使用對象字面量調用 `dispatch` : ```javascript // event handler 里的某處 dispatch({ type: 'ADD_TODO', text: 'Use Redux' }) ``` 你其實可以在單獨的文件中寫一個 action creator ,然后從 component 里 import: #### `actionCreators.js` ```javascript export function addTodo(text) { return { type: 'ADD_TODO', text } } ``` #### `AddTodo.js` ```javascript import { addTodo } from './actionCreators' // event handler 里的某處 dispatch(addTodo('Use Redux')) ``` Action creators 總被當作樣板代碼受到批評。好吧,其實你并不非得把他們寫出來!**如果你覺得更適合你的項目,你可以選用對象字面量** 然而,你應該知道寫 action creators 是存在某種優勢的。 假設有個設計師看完我們的原型之后回來說,我們最多只允許三個 todo 。我們可以使用 [redux-thunk](https://github.com/gaearon/redux-thunk) 中間件,并添加一個提前退出,把我們的 action creator 重寫成回調形式: ```javascript function addTodoWithoutCheck(text) { return { type: 'ADD_TODO', text } } export function addTodo(text) { // Redux Thunk 中間件允許這種形式 // 在下面的 “異步 Action Creators” 段落中有寫 return function(dispatch, getState) { if (getState().todos.length === 3) { // 提前退出 return } dispatch(addTodoWithoutCheck(text)) } } ``` 我們剛修改了 `addTodo` action creator 的行為,使得它對調用它的代碼完全不可見。**我們不用擔心去每個添加 todo 的地方看一看,以確認他們有了這個檢查** Action creator 讓你可以解耦額外的分發 action 邏輯與實際發送這些 action 的 components 。當你有大量開發工作且需求經常變更的時候,這種方法十分簡便易用。 ### Action Creators 生成器 某些框架如 [Flummox](https://github.com/acdlite/flummox) 自動從 action creator 函數定義生成 action type 常量。這個想法是說你不需要同時定義 `ADD_TODO` 常量和 `addTodo()` action creator 。這樣的方法在底層也生成了 action type 常量,但他們是隱式生成的、間接級,會造成混亂。因此我們建議直接清晰地創建 action type 常量。 寫簡單的 action creator 很容易讓人厭煩,且往往最終生成多余的樣板代碼: ```javascript export function addTodo(text) { return { type: 'ADD_TODO', text } } export function editTodo(id, text) { return { type: 'EDIT_TODO', id, text } } export function removeTodo(id) { return { type: 'REMOVE_TODO', id } } ``` 你可以寫一個用于生成 action creator 的函數: ```javascript function makeActionCreator(type, ...argNames) { return function(...args) { const action = { type } argNames.forEach((arg, index) => { action[argNames[index]] = args[index] }) return action } } const ADD_TODO = 'ADD_TODO' const EDIT_TODO = 'EDIT_TODO' const REMOVE_TODO = 'REMOVE_TODO' export const addTodo = makeActionCreator(ADD_TODO, 'text') export const editTodo = makeActionCreator(EDIT_TODO, 'id', 'text') export const removeTodo = makeActionCreator(REMOVE_TODO, 'id') ``` 一些工具庫也可以幫助生成 action creator ,例如 [redux-act](https://github.com/pauldijou/redux-act) 和 [redux-actions](https://github.com/acdlite/redux-actions) 。這些庫可以有效減少你的樣板代碼,并緊守例如 [Flux Standard Action (FSA)](https://github.com/acdlite/flux-standard-action) 一類的標準。 ## 異步 Action Creators [中間件](../Glossary.html#middleware) 讓你在每個 action 對象 dispatch 出去之前,注入一個自定義的邏輯來解釋你的 action 對象。異步 action 是中間件的最常見用例。 如果沒有中間件,[`dispatch`](../api/Store.md#dispatch) 只能接收一個普通對象。因此我們必須在 components 里面進行 AJAX 調用: #### `actionCreators.js` ```javascript export function loadPostsSuccess(userId, response) { return { type: 'LOAD_POSTS_SUCCESS', userId, response } } export function loadPostsFailure(userId, error) { return { type: 'LOAD_POSTS_FAILURE', userId, error } } export function loadPostsRequest(userId) { return { type: 'LOAD_POSTS_REQUEST', userId } } ``` #### `UserInfo.js` ```javascript import { Component } from 'react' import { connect } from 'react-redux' import { loadPostsRequest, loadPostsSuccess, loadPostsFailure } from './actionCreators' class Posts extends Component { loadData(userId) { // 調用 React Redux `connect()` 注入的 props : const { dispatch, posts } = this.props if (posts[userId]) { // 這里是被緩存的數據!啥也不做。 return } // Reducer 可以通過設置 `isFetching` 響應這個 action // 因此讓我們顯示一個 Spinner 控件。 dispatch(loadPostsRequest(userId)) // Reducer 可以通過填寫 `users` 響應這些 actions fetch(`http://myapi.com/users/${userId}/posts`).then( response => dispatch(loadPostsSuccess(userId, response)), error => dispatch(loadPostsFailure(userId, error)) ) } componentDidMount() { this.loadData(this.props.userId) } componentDidUpdate(prevProps) { if (prevProps.userId !== this.props.userId) { this.loadData(this.props.userId) } } render() { if (this.props.isFetching) { return <p>Loading...</p> } const posts = this.props.posts.map(post => ( <Post post={post} key={post.id} /> )) return <div>{posts}</div> } } export default connect(state => ({ posts: state.posts, isFetching: state.isFetching }))(Posts) ``` 然而,不久就需要再來一遍,因為不同的 components 從同樣的 API 端點請求數據。而且我們想要在多個 components 中重用一些邏輯(比如,當緩存數據有效的時候提前退出)。 **中間件讓我們能寫表達更清晰的、潛在的異步 action creators。** 它允許我們 dispatch 普通對象之外的東西,并且解釋它們的值。比如,中間件能 “捕捉” 到已經 dispatch 的 Promises 并把他們變為一對請求和成功/失敗的 action. 中間件最簡單的例子是 [redux-thunk](https://github.com/gaearon/redux-thunk). **“Thunk” 中間件讓你可以把 action creators 寫成 “thunks”,也就是返回函數的函數。** 這使得控制被反轉了: 你會像一個參數一樣取得 `dispatch` ,所以你也能寫一個多次分發的 action creator 。 > ##### 注意 > Thunk 只是一個中間件的例子。中間件不僅僅是關于 “分發函數” 的:而是關于你可以使用特定的中間件來分發任何該中間件可以處理的東西。例子中的 Thunk 中間件添加了一個特定的行為用來分發函數,但這實際取決于你用的中間件。 用 [redux-thunk](https://github.com/gaearon/redux-thunk) 重寫上面的代碼: #### `actionCreators.js` ```javascript export function loadPosts(userId) { // 用 thunk 中間件解釋: return function(dispatch, getState) { const { posts } = getState() if (posts[userId]) { // 這里是數據緩存!啥也不做。 return } dispatch({ type: 'LOAD_POSTS_REQUEST', userId }) // 異步分發原味 action fetch(`http://myapi.com/users/${userId}/posts`).then( response => dispatch({ type: 'LOAD_POSTS_SUCCESS', userId, response }), error => dispatch({ type: 'LOAD_POSTS_FAILURE', userId, error }) ) } } ``` #### `UserInfo.js` ```javascript import { Component } from 'react' import { connect } from 'react-redux' import { loadPosts } from './actionCreators' class Posts extends Component { componentDidMount() { this.props.dispatch(loadPosts(this.props.userId)) } componentDidUpdate(prevProps) { if (prevProps.userId !== this.props.userId) { this.props.dispatch(loadPosts(this.props.userId)) } } render() { if (this.props.isFetching) { return <p>Loading...</p> } const posts = this.props.posts.map(post => ( <Post post={post} key={post.id} /> )) return <div>{posts}</div> } } export default connect(state => ({ posts: state.posts, isFetching: state.isFetching }))(Posts) ``` 這樣打得字少多了!如果你喜歡,你還是可以保留 “原味” action creators 比如從一個容器 `loadPosts` action creator 里用到的 `loadPostsSuccess` 。 **最后,你可以編寫你自己的中間件** 你可以把上面的模式泛化,然后代之以這樣的異步 action creators : ```js 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 } } } ``` 解釋這個 actions 的中間件可以像這樣: ```javascript function callAPIMiddleware({ dispatch, getState }) { return next => action => { const { types, callAPI, shouldCallAPI = () => true, payload = {} } = action if (!types) { // Normal action: pass it on return next(action) } if ( !Array.isArray(types) || types.length !== 3 || !types.every(type => typeof type === 'string') ) { throw new Error('Expected an array of three string types.') } if (typeof callAPI !== 'function') { throw new Error('Expected callAPI to be a function.') } if (!shouldCallAPI(getState())) { return } const [requestType, successType, failureType] = types dispatch( Object.assign({}, payload, { type: requestType }) ) return callAPI().then( response => dispatch( Object.assign({}, payload, { response, type: successType }) ), error => dispatch( Object.assign({}, payload, { error, type: failureType }) ) ) } } ``` 在傳給 [`applyMiddleware(...middlewares)`](../api/applyMiddleware.md) 一次以后,你能用相同方式寫你的 API 調用 action creators : ```javascript export function loadPosts(userId) { return { types: ['LOAD_POSTS_REQUEST', 'LOAD_POSTS_SUCCESS', 'LOAD_POSTS_FAILURE'], shouldCallAPI: state => !state.users[userId], callAPI: () => fetch(`http://myapi.com/users/${userId}/posts`), payload: { userId } } } export function loadComments(postId) { return { types: [ 'LOAD_COMMENTS_REQUEST', 'LOAD_COMMENTS_SUCCESS', 'LOAD_COMMENTS_FAILURE' ], shouldCallAPI: state => !state.posts[postId], callAPI: () => fetch(`http://myapi.com/posts/${postId}/comments`), payload: { postId } } } export function addComment(postId, message) { return { types: [ 'ADD_COMMENT_REQUEST', 'ADD_COMMENT_SUCCESS', 'ADD_COMMENT_FAILURE' ], callAPI: () => fetch(`http://myapi.com/posts/${postId}/comments`, { method: 'post', headers: { Accept: 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify({ message }) }), payload: { postId, message } } } ``` ## Reducers Redux reducer 用函數描述邏輯更新減少了樣板代碼里大量的 Flux stores 。函數比對象簡單,比類更簡單得多。 這個 Flux store: ```javascript const _todos = [] const TodoStore = Object.assign({}, EventEmitter.prototype, { getAll() { return _todos } }) AppDispatcher.register(function(action) { switch (action.type) { case ActionTypes.ADD_TODO: const text = action.text.trim() _todos.push(text) TodoStore.emitChange() } }) export default TodoStore ``` 用了 Redux 之后,同樣的邏輯更新可以被寫成 reducing function: ```js export function todos(state = [], action) { switch (action.type) { case ActionTypes.ADD_TODO: const text = action.text.trim() return [...state, text] default: return state } } ``` `switch` 語句 _不是_ 真正的樣板代碼。真正的 Flux 樣板代碼是概念性的:發送更新的需求,用 Dispatcher 注冊 Store 的需求,Store 是對象的需求 (當你想要一個哪都能跑的 App 的時候復雜度會提升)。 不幸的是很多人仍然靠文檔里用沒用 `switch` 來選擇 Flux 框架。如果你不愛用 `switch` 你可以用一個單獨的函數來解決,下面會演示。 ### Reducers 生成器 寫一個函數將 reducers 表達為 action types 到 handlers 的映射對象。例如,如果想在 `todos` reducer 里這樣定義: ```javascript export const todos = createReducer([], { [ActionTypes.ADD_TODO]: (state, action) => { const text = action.text.trim() return [...state, text] } }) ``` 我們可以編寫下面的輔助函數來完成: ```javascript function createReducer(initialState, handlers) { return function reducer(state = initialState, action) { if (handlers.hasOwnProperty(action.type)) { return handlers[action.type](state, action) } else { return state } } } ``` 不難對吧?鑒于寫法多種多樣,Redux 沒有默認提供這樣的輔助函數。可能你想要自動地將普通 JS 對象變成 Immutable 對象,以填滿服務器狀態的對象數據。可能你想合并返回狀態和當前狀態。有多種多樣的方法來 “獲取所有” handler,具體怎么做則取決于項目中你和你的團隊的約定。 Redux reducer 的 API 是 `(state, action) => newState`,但是怎么創建這些 reducers 由你來定。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看