<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>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                [TOC] # 第 1 章 ## 1.JSX 的 onClick 事件處理方式和 HTML 的 onclick 有很大的不同: 在 HTML 中直接使用 onclick 存在的問題: * onclick 添加的事件處理函數是在全局環境下執行的,這污染了全局環境,很容易產生意料不到的后果 * 給很多 DOM 元素添加 onclick 事件,可能會影響網頁的性能,畢竟,網頁的事件處理函數越多,性能就會越低 * 對于使用 onclick 的 DOM 元素,如果要動態地從 DOM 樹中刪掉的話,需要把對應的事件處理器注銷,如果忘了注銷,就可能造成內存泄漏,這樣的 bug 很難被發現 上述問題在 JSX 中都不存在 * onClick 掛載的每個函數,都可以控制在組件范圍內,不會污染全局空間 * 我們在 JSX 中看到一個組件使用了 onClick,但并沒有產生直接使用 onclick 的HTML,而是使用了事件委托(event delegation)的方式處理點擊事件,無論有多少個 onClick 出現,其實最后都只在 DOM 樹上添加了一個事件處理函數,掛在最頂層的 DOM 節點上。所有的點擊事件都被這個事件處理函數捕獲,然后根據具體組件分配給特定函數,使用事件委托的性能當然要比為每個 onClick 都掛載一個事件處理函數要高 * 因為 React 控制了組件的生命周期,在 unmount 的時候自然能夠清除相關的所有事件處理函數,內存泄漏也不再是一個問題 ## 2.script 腳本中的 eject(彈射) 命令 執行`npm run eject`就是把潛藏在 react-scripts 中的一系列技術棧配置都 “彈射” 到應用的頂層,然后我們就可以研究這些配置細節,而且可以更靈活地定制應用的配置。 如 config 目錄下的 webpack.config.dev.js 文件,定制 npm start 所做的構造過程 ## 3.*UI=render(data)* React 的理念可歸結為這一公式。用戶看到的界面(UI),應該是一個函數(在這里叫 render)的執行結果,只接受數據(data)作為參數。這個函數是一個純函數,即沒有任何副作用,輸出完全依賴于輸入的函數,兩次函數調用如果輸入相同,得到的結果也絕對相同。如此一來,最終的用戶界面,在 render 函數確定的情況下完全取決于輸入數據 ?對于開發者來說,重要的是區分開哪些屬于 data,哪些屬于 render,想要更新用戶界面,要做的就是更新 data,用戶界面自然會作出響應。所以 React 實踐的也是“響應式編程”(Reactive Programming)的思想,React 的名字由此而來。 ## 4.Virutal DOM 的簡單描述 DOM 樹是對 HTML 的抽象,Virtual DOM 是對 DOM 樹的抽象。Virtual DOM 不會觸及瀏覽器的部分,只是存在于 JavaScript 空間的樹形結構,每次自上而下渲染 React 組件時,會對比這一次產生的 Virtual DOM 和上一次渲染的 Virtual DOM,對比就會發現差別,然后修改真正的 DOM 樹時就只需要觸及差別中的部分就行 # 第 2 章 設計高質量的 React 組件 ## 1.組件的劃分通則 組件的劃分要滿足高內聚(High Cohesion)和低耦合(Low Coupling)的原則 **高內聚** 指的是把邏輯緊密相關的內容放在一個組件中:傳統上,內容由 HTML 表示,交互行為放在 JavaScript 代碼文件中, 樣式放在 CSS 文件中定義,這雖然滿足一個工程模塊的需要,卻要放在三個不同的文件中。React 中,展示內容的 JSX、定義行為的 JavaScript、甚至定義樣式的 CSS,都可以放在一個 JavaScript 文件中,所以 React 天生具有高內聚的特點 **低耦合** 指不同組件之間的依賴關系要盡量弱化,也就是每個組件要盡量獨立。保持整個系統的低耦合度,需要對系統中的功能由充分的認識,然后根據功能點劃分模塊,讓不同的組件去實現不同的功能 ## 2.React 中的數據 React 組件的數據分為兩種,porp 和 state,prop 或者 state 改變都可能引發組件的重新渲染,那么設計一個組件的時候,什么時候用 porp,什么時候用 state 呢? prop 是組件的對外接口,state 是組件的內部狀態,對外用 prop,內部用 state prop 的類型不限于純數據,也可以是函數,函數類型的 prop 等于讓父組件給了子組件一個回調函數 可以通過回答以下問題來看看你是否對 React 較為熟悉 * 父組件用 prop 傳遞信息給子組件的形式? * 子組件如何讀取 prop 值? * 通過類的 propTypes 屬性定義 prop 規格的格式? 關于 propTypes 的使用需要注意一些問題:定義類的 propTypes 屬性,無疑是要占用一些代碼空間,而且類型檢查也是要消耗 CPU 計算資源的。其次,在線上環境做 propTypes 類型檢查沒有什么幫助,即在開發過程中為了避免犯錯我們才使用 propTypes,發布產品代碼時,可以用一種自動的方式將 propTypes 去掉。`babel-react-optimize`具有這個功能,可以通過過 npm 安裝,但是確保只在發布產品代碼的時候使用它。 <br /> state 需要在構造函數中初始化(通過對 state 的賦值),組件的 state 必須是一個 JavaScript 對象。通過`this.state`讀取當前組件的 state,通過`this.setState()`更新 state **prop 和 state 的對比** * prop 用于定義外部接口,state 用于記錄內部的狀態 * prop 的賦值在外部世界使用組件時,state 的賦值在組件內部 * 組件不應該改變 prop 的值,而 state 的存在目的就是讓組件來改變的。雖然 React 并沒有辦法阻止你去修改傳入的 props 對象,但是你仍然不該跨越這條紅線,否則最后可能出現不可預料的 bug # 第 3 章:從 Flux 到 Redux ## Flux 到 Redux ![](https://box.kancloud.cn/721176e45da9eb9aaf535d3d801db11f_825x473.png) 對于 MVC 框架,為了讓數據流可控,Controller 應該是中心,當 View 要傳遞消息給 Model 時,應該調用 Controller 的方法,同樣,當 Model 要更新 View 時,也應該通過 Controller 引發新的渲染 ![](https://box.kancloud.cn/96083da782507dc86984dea459d334b0_828x252.png) 一個 Flux 應用包含四個部分: * Dispatcher:處理動作分發,維持 Store 之間的依賴關系 * Store:負責存儲數據和處理數據相關邏輯 * Action:驅動 Dispatcher 的 JavaScript 對象 * View:視圖部分,負責顯示用戶界面 MVC 與 Flux 的對比:在 MVC 框架中,系統能夠提供什么樣的服務,通過 Controller 暴露函數來實現。每增加一個功能,Controller 往往就需要增加一個函數;在 Flux 的世界里,新增加功能并不需要 Dispatcher 增加新的函數,要做的就是增加一種新的 Action 類型, Dispatcher 的對外接口并不用改變。 當需要擴充應用所能處理的”請求“時,MVC 方法就需要增加新的 Controller,而對于 Flux 則只是增加新的 Action Flux 的優點就在于其 “單向數據流” 的管理方式,這種 “限制” 禁絕了數據流混亂的可能 其不足之處在于: * Store 之間的依賴關系 * 難以進行服務器端渲染 * Store 混雜了邏輯和狀態 > 感興趣的可以搜下使用 Flux 的狀態管理方案 ## Redux ![](https://box.kancloud.cn/23200e084e7147c03fd5718d9426a7e2_392x242.png) Flux 的基本原則是“單向數據流”,Redux 在此基礎上強調三個基本原則: * 唯一數據源(Single Source of Truth) * 保持狀態只讀(State is read-only) * 數據改變只能通過純函數完成(Changes are made with pure functions) 1.唯一數據源:應用的狀態數據應該只存儲在唯一的一個 Store 上,避免了狀態數據分散在多個 Store 中造成的數據冗余 2.保持狀態只讀:要修改 Store 的狀態,必須要通過派發一個 action 對象來完成 3.數據改變只能通過純函數完成:這里所說的純函數就是 Reducer,在 Redux 中,每個 reducer 的函數簽名如下:`reducer(state, action)` 第一個參數 state 是當前的狀態,第二個參數 action 是接收到的 action 對象,而 reducer 函數要做的事情,就是根據 state 和 action 的值產生一個新的對象返回。注意 reducer 必須是純函數,即函數的返回結果完全由參數 state 和 action 決定,而且不產生任何副作用,也不能修改參數 state 和 action 對象 ## 容器組件與傻瓜組件 在 Redex 框架下,一個 React 組件基本上就是要完成以下兩個功能: * 和 Redux Store 打交道,讀取 Store 的狀態,用于初始化組件的狀態,同時還要監聽 Store 的狀態改變;當 Store 狀態發生變化時,需要更新組件狀態,從而驅動組件重新渲染;當需要更新 Store 狀態時,就要派發 action 對象 * 根據當前 prop 和 state,渲染出用戶界面 >[warning] 疑問:現在有一種說法是所有組件的狀態全部丟到 Redux 的 Store 中進行管理,那么組件自身的“狀態”哪去了?Store 中狀態的更新是否必定觸發組件的重新渲染?個人的理解是:以 react-redux 的實現為例,我們在組件中是通過 this.props 來訪問 Store 中的數據和調用一些 dispatch 的,所以如果 Store 中的狀態改變了,以這些狀態為 props 的組件就會重新渲染(state 和 prop 的改變都可能觸發重新渲染) 如果 React 組件都是要包辦上面所說的兩個任務,似乎做的事情稍微多了點。所以我們可以考慮拆分為兩個組件,分別承擔一個任務,然后把兩個組件嵌套起來,完成原本一個組件完成的所有任務。 這樣的關系里,兩個組件是父子組件的關系。負責與 Redux Store 打交道的組件,處于外層,稱為容器組件(Container Component);只負責渲染界面的組件,處于內層,叫做展示組件(Presentational Component);外層的容器組件也叫作聰明組件(Smart Componnet),內層的展示組件又叫做傻瓜組件(Dumb Component) ![](https://box.kancloud.cn/96c3306bc62169cd87cb1838bc403e76_685x376.png =350x200) 狀態全部交由容器組件打理,展示組件只需要根據 props 來渲染結果,不需要 state;這只是設計 React組件的一種模式,和 Redux 沒有直接關系,另外,沒有 state 只有一個 render 方法,所有數據都來自于 props 的組件稱為 **無狀態組件**。 無狀態組件可以寫成一個函數,不再需要用對象表示 ```js // 解構賦值 function Counter ({caption, onIncrement, onDecrement, value}) { return ( <div> <button style={buttonStyle} onClick={onIncrement}>+</button> <button style={buttonStyle} onClick={onDecrement}>-</button> <span>{caption} count: {value}</span> </div> ); } // 不使用解構賦值 function Counter (props) { const {caption, onIcrement, onDecrement, value} = props // ... } ``` ## 組件 Context 不使用 react-redux 之前,每個組件都需要直接導入 Redux Store `import store from '../Store.js'` 雖然 Redux 應用全局就一個 Store,但是這樣的直接導入依然有問題(很麻煩) 為了解決這個問題,那就只能讓上層組件把 Store 傳遞下來,首先想到的是用 props,但是層層傳遞的方法顯然是不可行的。設想在一個嵌套多層的組件結構中,只有最里層的組件才需要使用 store,但是為了把 store 從最外層傳遞到最里層,就要求中間所有的組件都需要增加對這個 store prop 的支持,即使根本不使用它。 React 提供了一個叫 Context 的功能,能完美地解決這個問題。 所謂 Context,就是“上下文環境”,讓一個樹狀組件上所有組件都能訪問一個共同的對象。 ![](https://box.kancloud.cn/661c43a8cee1ad4795d06668172ab5ba_575x281.png) > 可以查閱 React 文檔了解如何使用 React 提供的 Context API:[https://react.docschina.org/docs/context.html](https://react.docschina.org/docs/context.html) 其實 react-redux 這個庫就是基于 Context 實現的。 這里分析下它使用的這條語句: ```js export default connect(mapStateToProps, mapDispatchToProps)(Counter) ``` connect 是 react-redux 提供的一個方法,這個方法接收兩個參數,執行結果依然是一個函數,所以才可以在后面又加一個圓括號(柯里化?),把 connect 函數執行的結果立即執行。 這里有兩次函數的執行,第一次是 connect 函數的執行,第二次是把 connect 函數返回的函數再次執行,最后產生的就是容器組件。 connect 函數具體做了哪些事呢? * 把 Store 上的狀態轉化為內層傻瓜組件的 prop * 把內層傻瓜組件中的用戶動作轉化為派送給 Store 的動作 這兩個工作一個是內層傻瓜對象的輸入,一個是內層傻瓜對象的輸出 mapStateToProps(命名是業界習慣)就是把 Store 上的狀態轉化為內層組件的 props,建立映射關系 mapDispatchToProps 把內層傻瓜組件暴露出來的函數類型的 prop 關聯上 dispatch 函數的調用 ``` // 第二個參數是直接傳遞給外層容器組件的 props function mapDispatchToProps(dispatch, ownProps) { return { onIncrement: () => { dispatch(Actions.increment(ownProps.caption)); }, onDecrement: () => { dispatch(Actions.decrement(ownProps.caption)); } } } ``` # 第四章 模塊化 React 和 Redux 應用 ## 代碼文件的組織方式 1.按角色組織 ```js reducers/ todoReducer.js filterReducer.js actions/ todoActions.js filterAction.js components/ todoList.js todoItem.js filter.js containers/ todoListContainer.js todoItemContainer.js filterContainer.js ``` - reducer 目錄包含所有 Redux 的 reducer - actions 目錄包含所有 action 構造函數 - components 目錄包含所有的傻瓜組件 - containers 目錄包含所有的容器組件 2.按功能組織 ```js todoList/ action.js actionType.js index.js reducer.js views/ component.js container.js filter/ action.js actionType.js index.js reducer.js views/ component.js container.js ``` - actionType.js 定義 action 類型 - action.js 定義 action 構造函數,決定了這個功能模塊可以接受的動作 - reducer.js 定義這個功能模塊如何響應 action.js 中定義的動作 - views 目錄包含這個功能模塊中所有的 React 組件,包括傻瓜組件和容器組件 - index.js 把所有的角色導入,然后統一導出 >[warning]個人感覺不太能夠接受這兩種組織文件的方式...... # 第 5 章 React 組件的性能優化 ## 引入性能檢測工具 React Pref `npm install react-addons-perf -D`:開發環境下性能檢測(哪些組件造成了無意義的渲染) `npm install redux-immutable-state-invariant -D`:開發環境下檢測 reducer 是否為純函數,不是則報錯 Store.js 文件 ```js import {createStore, combineReducers, applyMiddleware, compose} from 'redux'; import {reducer as todoReducer} from './todos'; import {reducer as filterReducer} from './filter'; // 下面三行代碼! import Perf from 'react-addons-perf' const win = window; win.Perf = Perf const reducer = combineReducers({ todos: todoReducer, filter: filterReducer }); const middlewares = []; // 考慮到將來的擴展,使用數組變量 middlewares 來存儲所有的中間件,之后的中間件直接 push if (process.env.NODE_ENV !== 'production') { // 使用 require 是因為 import 語句不能存在于條件語句中 middlewares.push(require('redux-immutable-state-invariant')()); // react-immutable-state-invariant 中間件只在開發環境下有意義,用于檢查 reducer 是否為純函數 } // Redux 提供 compose 函數把多個 Store Enhancer 組合在一起 const storeEnhancers = compose( applyMiddleware(...middlewares), (win && win.devToolsExtension) ? win.devToolsExtension() : (f) => f, // Redux Devtools 開發者工具 ); export default createStore(reducer, {}, storeEnhancers); // Store Enhancers 能夠讓 createStore 函數產生的 Store 對象具有更多的功能 ``` ## 單個組件的性能優化 更改 shouldComponentUpdate 函數的默認實現,根據每個 React 組件的內在邏輯定制其行為 ``` shouldComponentUpdate(nextProps, nextState) { // 假設影響渲染內容的 prop 只有 completed 和 text,只需要確保 // 這兩個 prop 沒有變化,函數就可以返回 false return (nextProps.completed !== this.props.completed) || (nextProps.text !== this.props.text) } ``` 使用 immutable.js 解決復雜數據 diff、clone 等問題。 immutable.js 實現原理:持久化數據結構,也就是使用舊數據創建新數據時,要保證舊數據同時可用且不變。同時為了避免 deepCopy 把所有節點都復制一遍帶來的性能損耗,Immutable 使用了結構共享,即如果對象樹中一個節點發生變化,只修改這個節點和受它影響的父節點,其它節點則進行共享。 ## 多個組件的性能優化(這部分主要介紹了虛擬 DOM 的原理) 用戶操作引發界面的更新并不會讓 React 生成的虛擬 DOM 推倒重來,React 在更新階段巧妙地對比原有的 Virtual DOM 和新生成的 Virtual DOM,找出兩者的不同之處,根據不同來修改 DOM 樹,這樣就只需要做最小的必要改動。這個“找不同”的過程,就叫做 Reconciliation(調和)。 React 對比兩個 Virtual DOM 的樹形結構時,從根節點開始遞歸往下比對,在樹形結構上,每個節點都可以看作這個節點以下部分子樹的根節點。所以這個比對算法可以從 Virtual DOM上任何一個節點開始執行。 React 首先檢查兩個樹形結構的根節點的類型是否相同,根據相同或者不同有不同處理方式 **1.節點類型不同的情況** 直接扔掉原來的,構建新的 DOM 樹,原有的樹形結構上的 React 組件會經歷“卸載”的生命周期,而取而代之的組件會經歷“裝載”的生命周期 ```html <div> <Todos /> </div> // 我們想要更新成這樣 <span> <Todos /> </span> ``` 比如上面的代碼在比較時,根節點類型不一樣,一切推倒重來,重新構建一個 span 節點及其子節點 作為開發者,需要避免這種浪費的情景出現(div 和 span 的子節點是一樣的) **2.節點類型相同的情況** 如果兩個樹形結構的根節點類型相同,React 就認為原來的根節點只需要更新過程,不會將其卸載,也不會引發根節點的重新裝載 這里有必要區分一下節點的類型:一類是 DOM 元素類型,一類是 React 組件;對于 DOM 元素類型, React 會保留節點對應的 DOM 元素,只對樹形結構根節點上的屬性和內容做一下比對,然后只更新修改的部分 ```html <div style={{color: 'red', fontSize: 15}} className="welcome"> Hello World </div> // 改變之后的 JSX,React 可以對比發現內容和屬性的變化,只修改這些變化的部分 <div style={{color: 'green', fontSize: 15}} className="farewell"> Hello World </div> ``` 如果屬性結構的根節點是 React 組件類型,React 能做的就是根據新節點的 props 取更新原來根節點的組件實例,即按順序觸發下列函數(舊生命周期) - shouldComponentUpdate - componentWillReceiveProps - componentWillUpdate - render - componentDidUpdate 處理完根節點的對比之后,會對根節點的每個子節點重復一樣的動作。 **3.多個子組件的情況** 當一個組件包含多個子組件的情況 ```html <ul> <TodoItem text="First" completed={false} /> <TodoItem text="Second" completed={false} /> </ul> // 更新為 <ul> <TodoItem text="Zero" completed={false} /> <TodoItem text="First" completed={false} /> <TodoItem text="Second" completed={false} /> </ul> ``` 直觀上看,只需要創建一個新組件,更新之前的兩個組件;但是實際情況并不是這樣的,React 并沒有找出兩個序列的精確差別,而是直接挨個比較每個子組件。 在上面的新的 TodoItem 實例插入在第一位的例子中,React 會首先認為把 text 為 First 的 TodoItem 組件實例的 text 改成了 Zero,text 為 Second 的 TodoItem 組件實例的 text 改成了 First,在最后面多出了一個 TodoItem 組件實例。這樣的操作的后果就是,現存的兩個實例的 text 屬性被改變了,強迫它們完成了一個更新過程,創造出來的新的 TodoItem 實例用來顯示 Second。 我們可以看到,理想情況下只需要增加一個 TodoItem 組件,但實際上其還強制引發了其他組件實例的更新。 假設有 100 個組件實例,那么就會引發 100 次更新,這明顯是一個浪費;所以就需要開發人員在寫代碼的時候提供一點小小的幫助,這就是接下來要講的 key 的作用 > 如果 React 采用的是先找出兩個序列的差異的算法,時間是 O(N^2),這不適合一個對性能要求很高的場景 ## key 的作用 ```html <ul> <TodoItem key={1} text="First" completed={false} /> <TodoItem key={2} text="Second" completed={false} /> </ul> // 新增一個 TodoItem 實例 <ul> <TodoItem key={0} text="Zero" completed={false} /> <TodoItem key={1} text="First" completed={false} /> <TodoItem key={2} text="Second" completed={false} /> </ul> ``` React 根據 key 值,就可以知道現在的第二個和第三個 TodoItem 實例其實就是之前的第一個和第二個實例,所以 React 就會把新創建的 TodoItem 實例插在第一位,對于原有的兩個 TodoItem 實例只用原有的 props 來啟動更新過程,這樣 shouldComponentUpdate 就會發生作用,避免無謂的更新操作; 了解了這些之后,我們就知道 key 值應該是 **唯一** 且 **穩定不變的** 比如用數組下標值作為 key 就是一個典型的錯誤,看起來 key 值是唯一的,但是卻不是穩定不變的 比如:[a, b, c] 值與下標的對應關系:a: 0 b:1 c:2 刪除a -> [b, c] 值與下標的對應關系 b:0 c:1 無法用 key 值來確定比對關系(新的 b 應該與舊的 b 比,如果按 key 值則是與 a 比) ![](https://box.kancloud.cn/843f670bb548fca2492bf347a2d0c3f6_501x135.png) > 需要注意,雖然 key 是一個 prop,但是接受 key 的組件并不能讀取到 key 的值,因為 key 和 ref 是 React 保留的兩個特殊 prop,并沒有預期讓組件直接訪問 ## 利用 reselect 提高數據選取的性能 跟 vuex 的 getter 原理類似,緩存數據? # 第 6 章 React 高級組件 ## 高階組件的概念及應用 高階組件(Higher Order Component,HOC)并不是 React 提供的某種 API,而是使用 React 的一種模式,用于增強現有組件的功能。 簡單來說,一個高階組件就是一個函數,這個函數接受一個組件作為輸入,然后返回一個新的組件作為結果,而且,返回的新組件擁有了輸入組件所不具有的功能。這里提到的組件指的并不是組件實例,而是一個組件類,也可以是一個無狀態組件的函數。 ```js import React from 'react' function removeUserProp(WrappedComponent) { return class WrappingComponent extends React.Component { render() { const {user, ...otherProps} = this.props return <WrappedComponent {...otherProps} /> } } } export default removeUserProp ``` 這樣一個高階組件做的工作非常簡單,它接受一個名為 WrappedComponent 的參數,表示一個組件類,這個函數返回一個新的組件,所做的事情和 WrappedComponent 一模一樣,只是忽略名為 user 的 prop。假如我們不希望某個組件接收到 user 的 prop,那么我們就不要直接使用這個組件,而是把這個組件作為參數傳遞給 removeUserProp 函數,然后把這個函數的返回結果當作組件來使用 ```js const NewComponent = removeUserProp(SampleComponent) ``` 定義高階組件的意義何在? - 重用代碼 - 修改現有 React 組件的行為,假設我們不想修改原有組件的內部邏輯,那么可以考慮使用高階組件 ## 以函數為子組件的模式 # Redxu 和服務器通信 ## 利用代理服務快速解決跨域請求問題 代理服務器的作用大概如下圖 ![](https://box.kancloud.cn/2c3151c5fe2669ff242614c04de86ef0_672x532.png) 使用 create-react-app 創造的應用已經具備了代理功能,只需要在 package.json 中添加如下一行 `"proxy": "http://www.weather.com.cn/"` 這一行配置告訴我們的應用,當接收到不是要求本地資源(localhost)的 HTTP 請求時,這個 HTTP 請求的協議和域名部分"替換"為 `http://www.weather.com.cn` 轉發出去(代理服服務器會根據配置分析 入 和 出 的關系,重新構建新的請求),并將收到的結果返還給瀏覽器,但是注意在線上環境應該開發自己的代理服務器(如 Nginx 的代理配置) ## 如何關聯異步的網絡請求和同步的 React 組件渲染 可行的方法是這樣的 - 在裝載過程中,如果組件沒有獲得服務器結果,就不顯示結果或者顯示一個“正在加載”之類的提示信息(在 componentDidMount 函數中發送請求) - 獲取了請求結果后,要引發組件的一次更新過程,讓該組件重新繪制自己的內容 就像下面這樣,即我們把請求返回的數據直接保存在 React 組件的 state 中 ```js import React from 'react'; //TODO: change to your city code according to http://www.weather.com.cn/ const cityCode = 101010100; class Weather extends React.Component { constructor() { super(...arguments); this.state = {weather: null}; } componentDidMount() { const apiUrl = `/data/cityinfo/${cityCode}.html`; fetch(apiUrl).then((response) => { if (response.status !== 200) { // 需要檢查狀態碼,因為 fetch 認為只要服務器返回一個合法的 HTTP 響應就算成功 throw new Error('Fail to get response with status ' + response.status); } // response.json 檢查返回的數據是否為 JSON 格式同時幫我們執行 JSON.parse(response.text) ? response.json().then((responseJson) => { this.setState({weather: responseJson.weatherinfo}); }).catch((error) => { this.setState({weather: null}); }); }).catch((error) => { this.setState({weather: null}); }); } render() { if (!this.state.weather) { return <div>暫無數據</div>; } const {city, weather, temp1, temp2} = this.state.weather; return ( <div> {city} {weather} 最低氣溫 {temp1} 最高氣溫 {temp2} </div> ) } } export default Weather; ``` ## 使用 redux-thunk 中間件 把狀態存放在組件中并不是一個很好的選擇, Redux 本身就是用來幫助管理應用狀態的,應該盡量把狀態存放在 Redux Store 中。 Redux 本身的設計理念是不允許異步操作的,所以就需要中間件如 redux-thunk、redux-saga 在 Redux 架構下,一個 action 對象在通過 store.dispatch 派發,在調用 reducer 函數之前,就會先經過一個中間件的環節,這就是產生異步操作的機會 ![](https://box.kancloud.cn/74a868650bc5c1dbd21501a90f9f371c_959x402.png) redux-thunk 的工作是檢查 action 對象是不是函數,如果不是函數就放行,完成普通 action 對象的生命周期,而如果發現 action 對象是函數,那就執行這個函數,并把 Store 的 dispatch 函數和 getState 函數作為參數傳遞到函數中去,不會讓這個異步 action 對象繼續往前派發到 reducer 函數 舉一個簡單的例子來介紹異步 action: ```js const increment = () => ({ type: ActionTypes.INCREMENT }) const incrementAsync = () => { return dispatch => { setTimeout(() => { dispatch(increment()) }, 1000) } } ``` 這個函數被 dispatch 函數派發之后,會被 redux-thunk 中間件執行,于是 setTimeout 函數就會發生作用,在 1s 后利用參數 dispatch 函數派發出同步 action 構造函數 increment 的結果。應用到發送 AJAX 請求中就是:action 對象函數可以通過 fetch 發起一個對服務器的異步請求,當得到服務器結果之后,通過參數 dispatch 把成功或者失敗的結果當作 action 對象再派發到 reducer 上(這次發送的是普通的 action 對象),最終驅動 Store 上狀態的改變。 雖然大致了解了原理,但使用時還要注意設計異步操作的模式,如設計 action: 一個訪問服務器的 action,至少要設計到三個 action 類型: - 表示異步操作已經開始的 action 類型 - 表示異步操作成功的 action 類型 - 表示異步操作失敗的 action 類型 當這三種類型的 action 對象被派發時,會讓 React 組件進入各自不同的三種狀態:(組件根據狀態來渲染不同的視圖) - 異步操作正在進行中 - 異步操作已經成功完成 - 異步操作已經失敗 ```js import {FETCH_STARTED, FETCH_SUCCESS, FETCH_FAILURE} from './actionTypes.js'; // 返回 type 字段以驅動 reducer 函數去改變 Redux Store 上的某個字段的狀態,從而驅動對應的 React 組件重新渲染 export const fetchWeatherStarted = () => ({ type: FETCH_STARTED }); export const fetchWeatherSuccess = (result) => ({ type: FETCH_SUCCESS, result }) export const fetchWeatherFailure = (error) => ({ type: FETCH_FAILURE, error }) export const fetchWeather = (cityCode) => { return (dispatch) => { const apiUrl = `/data/cityinfo/${cityCode}.html`; dispatch(fetchWeatherStarted()) // 派發一個普通 action 對象,將視圖置于"有異步 action 還未結束"的狀態 return fetch(apiUrl).then((response) => { if (response.status !== 200) { throw new Error('Fail to get response with status ' + response.status); } response.json().then((responseJson) => { dispatch(fetchWeatherSuccess(responseJson.weatherinfo)); // 派發一個表示請求成功的普通 action 對象 }).catch((error) => { dispatch(fetchWeatherFailure(error)); // 派發一個表示請求失敗的普通 action 對象 }); }).catch((error) => { dispatch(fetchWeatherFailure(error)); }) }; } ``` 再來看下 reducer 函數如何處理 ``` import {FETCH_STARTED, FETCH_SUCCESS, FETCH_FAILURE} from './actionTypes.js'; import * as Status from './status.js'; /* ./status.js export const LOADING = 'loading'; export const SUCCESS = 'success'; export const FAILURE = 'failure'; */ export default (state = {status: Status.LOADING}, action) => { switch(action.type) { case FETCH_STARTED: { return {status: Status.LOADING}; } case FETCH_SUCCESS: { return {...state, status: Status.SUCCESS, ...action.result}; } case FETCH_FAILURE: { return {status: Status.FAILURE}; } default: { return state; } } } ``` 異步 action 構造函數的模板: ``` export const sampleAsyncAction = () => { return (dispatch, getState) => { // 在這個函數里可以調用異步函數,自行決定在合適的時機通過 // 參數派發出新的 action 對象 } } ``` # Tips 1.在使用 JSX 的代碼文件中,即使代碼中并沒有直接使用 React,也一定要導入 React,因為 JSX 最終會被轉譯成依賴于 React 的表達式`React.createElement()` ***** 2.盡量避免使用 ref,ref 可以取得對元素的引用來訪問 DOM 元素,React 的產生就是為了避免直接操作 DOM 元素,因為直接訪問 DOM 元素很容易產生失控的情況。可以通過 **狀態綁定** 的方式來實現相應的功能,簡單來說,就是利用組件的狀態來存儲我們需要的內容。 ```js <input onChange={this.onInputChange} /> onInputChange(event) { this.setState({ value: event.target.value // 把內容存在組件狀態的 value 字段上 }) } ```
                  <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>

                              哎呀哎呀视频在线观看