隨著前端開發複雜度增加,原生開發模式顯得越來越笨重,前端框架也層出不窮。
MVC 和 MVVM
MVC
MVC是模型(model)-檢視(view)-控制器(controller)的縮寫,一種軟體設計典範,用一種業務邏輯、資料、介面顯示分離的方法組織程式碼,將業務邏輯聚集到一個部件裡面,在改進和個性化定製介面及使用者互動的同時,不需要重新編寫業務邏輯。MVC被獨特的發展起來用於對映傳統的輸入、處理和輸出功能在一個邏輯的圖形化使用者介面的結構中。
- Model(模型):資料。
- View(檢視):使用者介面。
- Controller(控制器):業務邏輯。
MVVM
MVVM是Model-View-ViewModel的簡寫。它本質上就是MVC 的改進版。MVVM 就是將其中的View 的狀態和行為抽象化,讓我們將檢視 UI 和業務邏輯分開。
採用雙向繫結(data-binding):View的變動,自動反映在 ViewModel,反之亦然。
React Component class程式設計
React 是一個 用於構建使用者介面的 JavaScript 庫,注重於 View 層。
React Component 並沒有嚴格的M,V區分,只是模糊的定義了幾塊內容:
- state: 資料存放
- render: 使用者介面
- setState | forceUpdate: 渲染使用者介面
所以我們的程式碼邏輯是這樣的:
- 定義state
- 根據state編寫render
- 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無關的變數可以放在函式外。