「ReactNaitve」對hooks最佳實踐的探索

author: 軒邈javascript

1、hooks介紹

一. useState

  1. eg: const [count, setCount] = useState(0)html

  2. 介紹java

    (1)有一個參數:默認值, 能夠是函數,只在初始渲染時執行一次node

    (2)返回一個帶有兩個元素的數組react

    (3)第一個元素是 state 的值, 第二個元素是更新state的函數,每次新的,使用useCallback,可讓它使用上次的函數;函數名隨意不必定是set某某某,參數能夠是要更新的值或者函數,當是函數時,函數參數是上一次state的值。git

(4) 若是有多個state則根據 useState 的調用順序來「記住」每一個state的狀態歸屬 
 
 (5)這個特性要求Hooks不能夠寫在if或者switch等可能不執行的代碼片斷,會致使調用次序不一致
複製代碼

二. useLayoutEffect

簽名與 useEffect 相同,在DOM變化(layout佈局)後同步觸發,渲染之(paint繪製)執行 ,適用於用戶可見的 DOM 改變。github

三. useEffect

componentDidMountcomponentDidUpdate 不一樣,傳遞給 useEffect 的函數在DOM變化(layout佈局) 和渲染(paint繪製)觸發。 這使得它適用於許多常見的 side effects ,例如設置訂閱和事件處理程序,由於大多數類型的工做不該阻止瀏覽器更新(判斷標準)屏幕。redux

1. eg:
`componentDidMount:  useLayoutEffect(() => {setTitle(1)}, [])`   

  `componentDidUpdate:  useLayoutEffect(() => {console.log(1)})` 

  `componentWillUnmount: useLayoutEffect()=>{return () => {console.log('我要卸載組件啦')}}` 

  我是一個方法,組件更新後返回,下次組件更新前執行:`useLayoutEffect(() => {return () => {console.log(‘我是一個方法,組件更新後返回,下次組件更新前執行')}}, [count, count2])` 
複製代碼
2. 介紹:至關於 componentDidMount 、 componentDidUpdate 、componentWillUnmount和 我是一個方法組件更新後返回,下次組件更新前執行
3. 有兩個參數
1. 第一個爲函數,默認會在渲染完成後執行一次,若是返回的是一個函數,則返回的函數會在第二個參數數組裏面的元素髮生變化且渲染完成後執行 
  2. 第二個爲一個數組(也能夠寫成常量等類型,不過不會調用參數一),裏面寫須要監控的state,當state改變時會調用第一個函數,不改變則不會調用參數一,當數組爲空時,只會在最開始調用一次,至關於componentDidMount;不傳時,默認監控全部state,至關於componentDidUpdate
複製代碼

四. useContext

1. eg:
const theme = useContext(ThemeContext) 
複製代碼
2. 使用 Contex時
```javascript
  const ThemeContext = React.createContext();
  const LanguageContext = React.createContext();
  ```

  ```js
  <ThemeContext.Consumer>
      {
          theme => (
              <LanguageContext.Cosumer>
                  language => {
                      //可使用theme和lanugage了
                  }
              </LanguageContext.Cosumer>
          )
      }
  </ThemeContext.Consumer>
  ```

  兩個render props寫法,兩個嵌套看起來麻煩不少


  使用useContext時 

  ```js
  const theme = useContext(ThemeContext);
  const language = useContext(LanguageContext);
  // 這裏就能夠用theme和language了
  ```

  接受一個由React.createContext返回的上下文對象,寫法簡化不少而且再也不須要理解render props 
複製代碼
3. 有時候會形成意想不到的從新渲染
```javascript
  const ThemedPage = () => {
      const theme = useContext(ThemeContext);
      return (
         <div>
              <Header color={theme.color} />
              <Content color={theme.color}/>
              <Footer color={theme.color}/>
         </div>
      );
  };
  ```

  當theme的其餘屬性(如size等其餘非color屬性)改變時也會致使界面從新渲染 
複製代碼

五. useReducer

1. 介紹:加強版的useState, 小型Redux (機制相似,可是不一樣組件內部數據認識獨立的,)
2. eg:
```javascript
  const initialState = { count: 0 } 
  const reducer = function reducer(state, action) { 
    switch (action.type) { 
      case 'reset': 
      	return initialState 
      case 'increment': 
      	return { ...state, count: state.count + 1 } 
      case 'decrement': 
      	return { ...state, count: state.count - 1 } 
      default: 
      	return state 
    }
  }
  ```

  //上面這部分應該寫在函數組件外面防止函數一遍遍的建立

  const [count3, count3Dispatch] = useReducer(reducer, initialState) 
複製代碼
3. 和useState相比dispatch和reducer只被建立了一次

六. useCallback

1. 介紹:返回值爲 memoized 回調函數,第一個參數爲一個函數,第二個爲一個數組,只有內部元素變化,返回的回調函數纔會被從新建立
2. eg:
```javascript
  const [count4, setCount4] = useState(0) 
  const counterRef = useRef(count4) 
  
  useEffect(
    () => { 
      counterRef.current = count4 
    }, 
    [count4] 
  )
  
  const incrementCount4 = useCallback(() => setCount4(counterRef.current + 1), []) 
  ```
複製代碼
  1. 儘可能少用,通常都能用useReducer優化

七. useMemo

介紹:useCallback(fn, inputs) 等價於 useMemo(() => fn, inputs)小程序

八. useRef

介紹:useRef 返回一個可變的 ref 對象,其 .current 屬性被初始化爲傳遞的參數(initialValue)。返回的對象將存留在整個組件的生命週期中。react-native

九. useImperativeMethods

  1. useImperativeMethods 自定義使用 ref 時公開給父組件的實例值,方便對函數式組件進行ref操做 使調用內部函數成爲可能
  2. 應儘可能避免這種代碼

十. 自定義Hooks興起可造成一種約定,代碼之間的公用邏輯可使用useXXX形式的函數

```javascript
// eg:  
const useMountLog = name => {
    useEffect(() => {
        console.log(`${this.name}組件渲染時間->${this.end - this.begin}ms`)
    },[])
}
```
複製代碼
// eg: usePrevious 
function usePrevious(value) {
  const ref = useRef()
  useEffect(() => {
    ref.current = value
  })
  return ref.current
}
複製代碼

2、環境構建

  1. git clone github.com/facebook/re… (我使用時react版本爲16.6.1)
  2. cd react
  3. packages/shared目錄下全部文件中的enableHooks = false替換爲enableHooks = true
  4. 運行yarn install
  5. 運行yarn build -- --type=RN_OSS
  6. 等運行完畢後,將 build/react-native/ 下的內容替換 項目路徑/node_modules/react-native/Libraries/Renderer(我使用時react-native版本爲0.57.8)下的內容
  7. 項目中react版本爲16.7.0-alpha.2(16.7.0-alpha.0~16.7.0-alpha.2皆可,16.7.0正式版將hooks移除了)

3、原有項目改造

  1. 替換類組件

    // 改造前
    export default class HomeScene extends Component{...}
    // 改造後
    export default forwardRef((props, ref) => {...}) // forwardRef包起來是方便函數組件內部方法調用
    複製代碼
  2. 構造函數移除

  3. state狀態修改和建立改成useState const [visible, setVisible] = useState(false)

  4. 改造前父組件經過ref引用子組件的方法,改造後使用forwardRef將createRef建立的ref傳遞到子組件內部,再使用useImperativeMethods將內部方法綁定到傳進來的ref上

    useImperativeMethods(ref, () => ({ showModal: this.showModal }), [])
    複製代碼
  5. 方法

    // 改造前
    showModal = (from, data, ticket) => {...}
    // 改造後
    this.showModal = (from, data, ticket) => {...} // 推薦使用箭頭函數方便函數間調用
    複製代碼
  6. render改成return,按state狀態變化將原先render前的邏輯移入對應useEffect。

  7. 引入redux-react-hook

    (1)引入StoreContext將根組件包起來

    <StoreContext.Provider value={store}>
    	...
    </StoreContext.Provider>
    複製代碼

    (2)```javascript // 改造前 const { name, cityName } = this.props.UserInfo // 改造後 const mapState = React.useCallback(state => state.UserInfo, []) const { name, cityName } = useMappedState(mapState)

    (3)每次調用useMappedState都會執行subscribe store。 可是,若是store更新,你的組件將只從新渲染一次。 所以,屢次調用useMappedState(例如封裝在自定義hooks中)不該該對性能產生很大影響。 若是測試發現性能影響較大,能夠嘗試返回對象。
    
    複製代碼
  8. 引入react-navigation-hooks,需升級react-navigation至最新版(3.1.0)適配。

(1)使用

```javaScript
  const { navigate } = useNavigation()
  ```

  與原先的路由管理共用一套路由
複製代碼

(2)注意:

1) 若是項目是用pod管理,該RNGestureHandler.podspec裏面路徑有問題須要修改。
  
  2) createBottomTabNavigator的第二個參數BottomTabNavigatorConfig的navigationOptions屬性改成了defaultNavigationOptions
複製代碼

4、注意事項

  1. 原先的組件加強基礎設施,如高階組件和反向集成之類的組件須要進行相應修改以適應新的函數組件。
  2. forwarfRef須要額外關注,由於forwardRef包裹組件後返回的是React節點須要單獨處理target.$$typeof === Symbol.for('react.forward_ref')(我暫時是這樣處理的)
  3. 須要注意加強組件的name或displayName設置,方便錯誤定位
  4. eslint-plugin-react-hooks: (1)在每一個渲染上以相同的順序調用 Hooks (不能在循環、判斷、switch中使用)。 (2)對 Hooks 的調用要麼在 PascalCase 函數內(假設是一個組件),要麼是另外一個 useSomething 函數(假定爲自定義Hook)。
  5. 私覺得hooks最大的亮點仍是在於邏輯複用的易用性提高上,這個須要咱們及時轉變思路,習慣函數式組件開發。
  6. 若是inputs爲[](useCallback的第二個參數),React每次渲染都將使用一樣的函數返回值(無論裏面的運算),這時你能夠選擇在函數組件外聲明這個函數,可是React官方並不推薦這樣作,hooks的重點就是是容許你保留組件中的全部內容。
  7. 咱們對組件加強時,組件的回調通常不須要銷燬監聽,並且僅需監聽一次,這與 DOM 監聽不一樣,所以大部分場景,咱們須要利用 useCallback 包裹,並傳一個空數組,來保證永遠只監聽一次,並且不須要在組件銷燬時註銷這個 callback。
  8. hooks目前還有不少坑,是趨勢,可瞭解,慎用。

原文連接: tech.meicai.cn/detail/81, 也可微信搜索小程序「美菜產品技術團隊」,乾貨滿滿且每週更新,想學習技術的你不要錯過哦。

相關文章
相關標籤/搜索