React 16.8 於 2019.2 正式發佈,這是一個能提高代碼質量和開發效率的特性,筆者就拋磚引玉先列出一些實踐點,但願獲得你們進一步討論。前端
然而須要理解的是,沒有一個完美的最佳實踐規範,對一個高效團隊來講,穩定的規範比合理的規範更重要,所以這套方案只是最佳實踐之一。react
Function Component 採用 const
+ 箭頭函數方式定義:git
const App: React.FC<{ title: string }> = ({ title }) => { return React.useMemo(() => <div>{title}</div>, [title]); }; App.defaultProps = { title: 'Function Component' }
上面的例子包含了:github
React.FC
申明 Function Component 組件類型與定義 Props 參數類型。React.useMemo
優化渲染性能。App.defaultProps
定義 Props 的默認值。爲何不用 React.memo?
推薦使用 React.useMemo
而不是 React.memo
,由於在組件通訊時存在 React.useContext
的用法,這種用法會使全部用到的組件重渲染,只有 React.useMemo
能處理這種場景的按需渲染。typescript
沒有性能問題的組件也要使用 useMemo 嗎?
要,考慮將來維護這個組件的時候,隨時可能會經過 useContext
等注入一些數據,這時候誰會想起來添加 useMemo
呢?npm
爲何不用解構方式代替 defaultProps?
雖然解構方式書寫 defaultProps
更優雅,但存在一個硬傷:對於對象類型每次 Rerender 時引用都會變化,這會帶來性能問題,所以不要這麼作。編程
局部狀態有三種,根據經常使用程度依次排列: useState
useRef
useReducer
。redux
const [hide, setHide] = React.useState(false); const [name, setName] = React.useState('BI');
狀態函數名要表意,儘可能彙集在一塊兒申明,方便查閱。api
const dom = React.useRef(null);
useRef
儘可能少用,大量 Mutable 的數據會影響代碼的可維護性。微信
但對於不需重複初始化的對象推薦使用 useRef
存儲,好比 new G2()
。
局部狀態不推薦使用 useReducer
,會致使函數內部狀態過於複雜,難以閱讀。 useReducer
建議在多組件間通訊時,結合 useContext
一塊兒使用。
能夠在函數內直接申明普一般量或普通函數嗎?
不能夠,Function Component 每次渲染都會從新執行,常量推薦放到函數外層避免性能問題,函數推薦使用 useCallback
申明。
全部 Function Component 內函數必須用 React.useCallback
包裹,以保證準確性與性能。
const [hide, setHide] = React.useState(false); const handleClick = React.useCallback(() => { setHide(isHide => !isHide) }, [])
useCallback
第二個參數必須寫,eslint-plugin-react-hooks 插件會自動填寫依賴項。
發請求分爲操做型發請求與渲染型發請求。
操做型發請求,做爲回調函數:
return React.useMemo(() => { return ( <div onClick={requestService.addList} /> ) }, [requestService.addList])
渲染型發請求在 useAsync
中進行,好比刷新列表頁,獲取基礎信息,或者進行搜索, 均可以抽象爲依賴了某些變量,當這些變量變化時要從新取數 :
const { loading, error, value } = useAsync(async () => { return requestService.freshList(id); }, [requestService.freshList, id]);
簡單的組件間通訊使用透傳 Props 變量的方式,而頻繁組件間通訊使用 React.useContext
。
以一個複雜大組件爲例,若是組件內部拆分了不少模塊, 但須要共享不少內部狀態 ,最佳實踐以下:
export const StoreContext = React.createContext<{ state: State; dispatch: React.Dispatch<Action>; }>(null) export interface State {}; export interface Action { type: 'xxx' } | { type: 'yyy' }; export const initState: State = {}; export const reducer: React.Reducer<State, Action> = (state, action) => { switch (action.type) { default: return state; } };
import { StoreContext, reducer, initState } from './store' const AppProvider: React.FC = props => { const [state, dispatch] = React.useReducer(reducer, initState); return React.useMemo(() => ( <StoreContext.Provider value={{ state, dispatch }}> <App /> </StoreContext.Provider> ), [state, dispatch]) };
import { StoreContext } from './store' const app: React.FC = () => { const { state, dispatch } = React.useContext(StoreContext); return React.useMemo(() => ( <div>{state.name}</div> ), [state.name]) };
如上解決了 多個聯繫緊密組件模塊間便捷共享狀態的問題 ,但有時也會遇到須要共享根組件 Props 的問題,這種不可修改的狀態不適合一併塞到 StoreContext
裏,咱們新建一個 PropsContext
注入根組件的 Props:
const PropsContext = React.createContext<Props>(null) const AppProvider: React.FC<Props> = props => { return React.useMemo(() => ( <PropsContext.Provider value={props}> <App /> </PropsContext.Provider> ), [props]) };
好比當輸入框頻繁輸入時,爲了保證頁面流暢,咱們會選擇在 onChange
時進行 debounce
。然而在 Function Component 領域中,咱們有更優雅的方式實現。
其實在 Input 組件onChange
使用debounce
有一個問題,就是當 Input 組件 受控 時,debounce
的值不能及時回填,致使甚至沒法輸入的問題。
咱們站在 Function Component 思惟模式下思考這個問題:
onChange
本不慢,大部分使用值的組件也不慢,沒有必要從 onChange
源頭開始就 debounce
。useDebounce
。下面是一個性能不好的組件,引用了變化頻繁的 text
(這個 text
多是 onChange
觸發改變的),咱們利用 useDebounce
將其變動的頻率慢下來便可:
const App: React.FC = ({ text }) => { // 不管 text 變化多快,textDebounce 最多 1 秒修改一次 const textDebounce = useDebounce(text, 1000) return useMemo(() => { // 使用 textDebounce,但渲染速度很慢的一堆代碼 }, [textDebounce]) };
使用 textDebounce
替代 text
能夠將渲染頻率控制在咱們指定的範圍內。
事實上,useEffect
是最爲怪異的 Hook,也是最難使用的 Hook。好比下面這段代碼:
useEffect(() => { props.onChange(props.id) }, [props.onChange, props.id])
若是 id
變化,則調用 onChange
。但若是上層代碼並無對 onChange
進行合理的封裝,致使每次刷新引用都會變更,則會產生嚴重後果。咱們假設父級代碼是這麼寫的:
class App { render() { return <Child id={this.state.id} onChange={id => this.setState({ id })} /> } }
這樣會致使死循環。雖然看上去 <App>
只是將更新 id 的時機交給了子元素 <Child>
,但因爲 onChange
函數在每次渲染時都會從新生成,所以引用老是在變化,就會出現一個無限死循環:
新 onChange
-> useEffect
依賴更新 -> props.onChange
-> 父級重渲染 -> 新 onChange
...
想要阻止這個循環的發生,只要改成 onChange={this.handleChange}
便可,useEffect
對外部依賴苛刻的要求,只有在總體項目都注意保持正確的引用時才能優雅生效。
然而被調用處代碼怎麼寫並不受咱們控制,這就致使了不規範的父元素可能致使 React Hooks 產生死循環。
所以在使用 useEffect
時要注意調試上下文,注意父級傳遞的參數引用是否正確,若是引用傳遞不正確,有兩種作法:
useCurrentValue
對引用老是變化的 props 進行包裝:function useCurrentValue<T>(value: T): React.RefObject<T> { const ref = React.useRef(null); ref.current = value; return ref; } const App: React.FC = ({ onChange }) => { const onChangeCurrent = useCurrentValue(onChange) };
onChangeCurrent
的引用保持不變,但每次都會指向最新的 props.onChange
,從而能夠規避這個問題。
若是還有補充,歡迎在文末討論。
如需瞭解 Function Component 或 Hooks 基礎用法,能夠參考往期精讀:
討論地址是: 精讀《React Hooks 最佳實踐》 · Issue #202 · dt-fe/weekly
若是你想參與討論,請 點擊這裏,每週都有新的主題,週末或週一發佈。前端精讀 - 幫你篩選靠譜的內容。
關注 前端精讀微信公衆號
<img width=200 src="https://img.alicdn.com/tfs/TB...;>
版權聲明:自由轉載-非商用-非衍生-保持署名( 創意共享 3.0 許可證)