從 React 架構開始講解 useState、useEffect 程式設計設計

whosmeya發表於2020-06-01

隨著前端開發複雜度增加,原生開發模式顯得越來越笨重,前端框架也層出不窮。

MVC 和 MVVM

MVC

MVC是模型(model)-檢視(view)-控制器(controller)的縮寫,一種軟體設計典範,用一種業務邏輯、資料、介面顯示分離的方法組織程式碼,將業務邏輯聚集到一個部件裡面,在改進和個性化定製介面及使用者互動的同時,不需要重新編寫業務邏輯。MVC被獨特的發展起來用於對映傳統的輸入、處理和輸出功能在一個邏輯的圖形化使用者介面的結構中。

  • Model(模型):資料。
  • View(檢視):使用者介面。
  • Controller(控制器):業務邏輯。
從 React 架構開始講解 useState、useEffect 程式設計設計

MVVM

MVVM是Model-View-ViewModel的簡寫。它本質上就是MVC 的改進版。MVVM 就是將其中的View 的狀態和行為抽象化,讓我們將檢視 UI 和業務邏輯分開。

採用雙向繫結(data-binding):View的變動,自動反映在 ViewModel,反之亦然。

從 React 架構開始講解 useState、useEffect 程式設計設計

React Component class程式設計

React 是一個 用於構建使用者介面的 JavaScript 庫,注重於 View 層。

React Component 並沒有嚴格的M,V區分,只是模糊的定義了幾塊內容:

  • state: 資料存放
  • render: 使用者介面
  • setState | forceUpdate: 渲染使用者介面

所以我們的程式碼邏輯是這樣的:

  1. 定義state
  2. 根據state編寫render
  3. render中加入事件,修改state,且渲染使用者介面

以上1,2兩步完成後,我們就不再需要關心render,因為render依賴state,我們只需要關心如何修改state,然後需渲染時,setState | forceUpdate就可以了。

生命週期 componentDidMount 也是很重要的,它再元件完成後只執行一次, 可以用於請求資料,然後設定state。

渲染頁面(setState):state -> view。

書寫思路清晰的程式碼,要清晰的知道資料的流向,我們這樣設計。

  • 初始化階段:框架自動渲染一次 -> componentDidMount -> 手動渲染
  • 使用者操作:操作 -> 修改state -> 手動渲染

總結:寫好render和state對應的規則後,只需要專心與如何修改state,然後執行渲染即可。

例子:列表請求

請求與請求引數的分離也是程式碼清晰程度的重要一部分。

setState最重要的還有第二個引數,是設定成功後的回撥函式。React的state可以讓我們專心開發某一塊,例如我們寫一個列表

state = {
  page: 1,
  dataList: null,
}

// 請求列表
fetchDataList = () => {
  const { page } = this.state;
  let data = '通過page引數請求得到的資料';  // 通過請求得到資料
  this.setState({ dataList: data });
}

// 翻頁
handlePageChange = (page) => {
  this.setState({ page }, this.fetchDataList);
}

寫一個請求方法,請求得到的引數完全從state中獲取,得到資料後會setState渲染頁面,所以我們只需要專心致志於設定state,在回撥中傳送請求。這樣,一切都看起來那麼清晰。

特殊使用

由於 state 是引用型別,所以我們可以使用 this.state.xx = xx 來修改資料,React 官方並不推薦此種修改方式,因為此方法並沒有渲染頁面,並不能直接的感受到資料的變化。

瞭解了 React 渲染機制後,只要清晰我們再做什麼,也可以使用此種方法修改資料,並且大量能減少程式碼量。

例如:頁面上有兩個按鈕,一個按鈕記錄此按鈕點選次數,另一個按鈕點選後,才會顯示第一個按鈕的點選次數。

使用常規setState方式,需要兩個變數計數。

state = {
  clickCount: 0;
  viewCount: 0;
}

btn1Click = () => {
  this.setState({clickCount: this.state.clickCount +1 });
}

btn2Click = () => {
  this.setState({viewCount: this.state.clickCount });
}

render() {
  return <div>{this.state.viewCount}</div>
}

如果使用隱士賦值,只需要一個變數,並且再需要渲染的時候手動渲染。

state = {
  count: 0;
}

btn1Click: () => {
  this.state.count++;
}

btn2Click: () => {
  this.forceUpdate(); // 強制渲染 相當於 this.setState({})
}

render() {
  return <div>{this.state.viewCount}</div>
}

當然,這種方式要在對 React 渲染機制清晰後再使用。

這就體現了React的靈活性,按需渲染。


React Hooks 函數語言程式設計

React 16.7推出了 React Hooks 函數語言程式設計。不用傳統的類方式,寫法大有不同。

首先看渲染機制,Component方式,渲染後,只執行了render方法,類裡面的其他方法不會執行。而 React Hooks 函數語言程式設計 每次渲染,都會把整個函式執行一遍,並提供了一個資料存放地 useState。

useState

// 宣告一個叫 "count" 的 state 變數
const [count, setCount] = useState(0);

setCount 用來設定 count 並且渲染頁面,且只有這一種渲染方式,這就意味著,我們不能像 Component 那樣靈活的按需渲染了。

useEffect

useEffect(function () {
  // do sth..
}, [])

useEffect 第一個引數是一個函式,滿足條件後會觸發。第二個引數是個陣列,如果是個空陣列則只執行一次第一個引數函式(相當於componentDidMount),如果裡面放變數,執行一次後,以後每次渲染後就監聽變數有沒有改變,如果改變就執行第一個函式。

與 class 方式的對比

對比 React.Component 和 React Hooks,它們都有存放資料的state,通過state渲染頁面的render,和手動渲染的方法setState或者setXXX。

不同的是,React.Component有setState成功後的回撥,React Hooks沒有。

例如使用 React Hooks 執行下面程式碼

setCount(2);
console.log(count);

count拿到的總是設定前的值。

useState、useEffect程式碼設計

看到知乎上一句話:先做什麼再做什麼這種callback的寫法是傾向於命令式,而使用hooks編寫程式碼則更傾向於宣告式.你不需要去指定你要的動作發生的時機, 而是宣告一個條件或者依賴來讓React來決定正確的執行時間點。

所以我們要轉變思路,不要去控制何時渲染頁面,因為每一次set都會渲染頁面,需要的是在useEffect裡寫條件,讓React自己決定渲染。

如請求改造如下

const [page, setPage] = useEffect(1);          // 請求引數 page
const [pageSize, setPageSize] = useEffect(20); // 請求引數 pageSize
const [type, setType] = useEffect(1);          // 請求引數 type
const [dataList, setDataList] = useEffect(1);  // 請求得到的資料

useEffect(function () {
  fetchDataList();
}, [page, pageSize, type]);

const fetchDataList = function () {
  let data = '通過page pageSize type請求到的資料';
  setDataList(data);
}

元件第一次執行或者page,pageSize,type改變,就會請求資料,然後set新資料渲染頁面。

上面程式碼基本上滿足了我們需要,然後在極端情況下,即使請求引數改變,也不需要發請求。對此我們需要另外設定一個變數控制是否發請求。

const [sendRequest, setSendRequest] = useEffect(0);  // 控制發請求

useEffect(function () {
  fetchDataList();
}, [sendRequest]);

const handlePageChange = (page) => {
  setPage(page);
  setSendRequest(Math.random());
}

但是這種寫法還是運用了命令式,違背了React Hooks本意,不推薦。推薦規則寫在useEffect中。


渲染優化

不管是 class 方式還是函數語言程式設計,都需要關心一個問題:合理渲染。

class 方式在每次 setState 或者 forceUpdate 都會執行render函式渲染。

函數語言程式設計方式 在useState中每次set新資料後,就會重新執行整個函式並渲染。

React 重要特徵是,一般情況下,父元件渲染,子元件也會渲染。所以在頂層容器中,要合理渲染,儘可能的抽成更小的元件,防止不必要的渲染。

class 方式中,state只放與rander有關的變數,無關的可以放在class外,減少setState的使用。函數語言程式設計一樣,和return無關的變數可以放在函式外。


whosmeya.com

相關文章