React 也能“用上”computed 屬性

React 也能 “用上” computed 屬性

來源:https://juejin.im/post/5db428ba518825077c432911

React 也能“用上”computed 屬性

前言,關於計算屬性

初次見到計算屬性一詞,是在 Vue 官方文檔 《計算屬性和偵聽器》 一節中,文章中是這樣描述計算屬性的:

模板內的表達式非常便利,但是設計它們的初衷是用於簡單運算的。在模板中放入太多的邏輯會讓模板過重且難以維護。

回想我們編寫的 React 代碼,是否也在 JSX(render 函數)中放入了太多的邏輯導致 render 函數過於龐大,難以維護?

React 中的計算屬性

說到 React 之前,我們先看下 Vue,在 Vue 中,計算屬性主要有以下兩點特性:

  1. 計算屬性以聲明的方式創建依賴關係,依賴的 data 或 props 變更會觸發重新計算並自動更新。
  2. 計算屬性是基於它們的響應式依賴進行緩存的。

而在 React 中,計算屬性也是經常可見,相信各位熟悉 React 的讀者都寫過類似下面的代碼:

import React, { Fragment, Component } from 'react'; 

class Example extends Component {
state = {
firstName: '',
lastName: '',
};
render() {
// 在 render 函數中處理邏輯
const { firstName, lastName } = this.state;
const fullName = `${firstName} ${lastName}`;
return <fragment>{fullName}/<fragment>;
}
}
複製代碼

在上面的代碼裡,render 函數里的 fullName 依賴了 props 中的 firstName 和 lastName 。firstName 或 lastName 變更之後,變量 fullName 都會自動更新。其實現原理是 props 以及 state 的變化會導致 render 函數調用,進而重新計算衍生值。

雖然能實現計算,但我們還是把計算邏輯放入了 render 函數導致了它的臃腫,這並不優雅。更好的做法是把計算邏輯抽出來,簡化 render 函數邏輯:

class Example extends Component {
state = {
firstName: '',
lastName: '',
};
// 把 render 中的邏輯抽成函數,減少render函數的臃腫
renderFullName() {
const { firstName, lastName } = this.state;
return `${firstName} ${lastName}`;
}
render() {
const fullName = this.renderFullName();
return <fragment>{fullName}/<fragment>;
}

}
複製代碼

如果你對 Vue 很瞭解,你肯定知道其 computed 計算屬性,底層是使用了getter,只不過是對象的 getter。那麼在 React 中,我們也可以使用類的 getter 來實現計算屬性:

class Example extends Component {
state = {
firstName: '',
lastName: '',
};
// 通過getter而不是函數形式,減少變量
get fullName() {
const { firstName, lastName } = this.state;
return `${firstName} ${lastName}`;
}
render() {
return <fragment>{this.fullName}/<fragment>;
}
}
複製代碼

進一步,使用 memoization 優化計算屬性

上文有提到在 Vue 中計算屬性對比函數執行,會有緩存,減少計算。因為計算屬性只有在它的相關依賴發生改變時才會重新求值。

這就意味著只要 firstName 和 lastName 還沒有發生改變,多次訪問 fullName 計算屬性會立即返回之前的計算結果,而不必再次執行函數。

對比之下,React 的 getter 是否也有緩存這個優勢???

答案是:沒有。React 中的 getter 並沒有做緩存優化

不過不用失望,我們可以使用記憶化技術(memoization)來優化我們的計算屬性,達到和 Vue 中計算屬性一樣的效果。我們需要在項目中引入 memoize-one 庫,代碼如下:

import memoize from 'memoize-one';
import React, { Fragment, Component } from 'react';
class Example extends Component {
state = {
firstName: '',
lastName: '',
};
// 如果和上次參數一樣,`memoize-one` 會重複使用上一次的值。
getFullName = memoize((firstName, lastName) => `${firstName} ${lastName}`);
get fullName() {
return this.getFullName(this.state.firstName, this.state.lastName);
}
render() {
return <fragment>{this.fullName}/<fragment>;
}
}
複製代碼

再進一步,使用 React Hooks 優化計算屬性

上文在 React 中使用了 memoize-one 庫實現了類似 Vue 計算屬性(computed)的效果 —— 基於依賴緩存計算結果。得益於React 16.8 新推出的 Hooks 特性,我們可以對邏輯進行更優雅的封裝,對 Hooks 還不夠了解的小夥伴可以先閱讀我們團隊另一篇文章 《看完這篇,你也能把 React Hooks 玩出花》

此處,我們需要用到 useMemo。官方對 useMemo 的介紹在 這裡,詳情請移步查看。簡單的說,就是我們傳入一個

回調函數 和一個 依賴列表,React 會在依賴列表中的值變化時,調用這個回調函數,並將回調函數返回的結果進行緩存:

import React, { useState, useMemo } from 'react';
function Example(props) {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
// 使用 useMemo 函數緩存計算過程
const renderFullName = useMemo(() => `${firstName} ${lastName}`, [
firstName,
lastName,
]);
return
{renderFullName}
;
}
複製代碼

總結

本文介紹了在 React 中如何實現類似 Vue 計算屬性(computed)的效果 —— 基於依賴緩存計算結果,實現邏輯計算與視圖渲染的解耦,降低 render 函數的複雜度。

從業務開發角度來講,Vue 提供的 API 極大地提高了開發效率。React 雖然在某些場景下,沒有官方的同類原生 API 支持,但得益於活躍的社區,工作中遇到的問題總能找到解決方案。且在摸索這些解決方案的同時,我們還能學習到諸多經典的編程思想,幫助我們更合理的運用框架,用技術解決業務問題。


分享到:


相關文章: