在react native中使用hooks

又在寫bug發表於2019-04-03

在react native中使用hooks
Facebook 於本月 12 號釋出了 React Native v0.59,支援了hooks 的使用。讓我們一起看下如何使用吧

什麼是hooks ?

官方原話

Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class

說白了就是在react 函式元件中,也可以使用類元件(classes components)的 state 和 元件生命週期。

why hooks ?

複用問題

複用一個有狀態的元件太難了,但是還是能複用的。官方怎麼做的呢?renderPropshoc,但是你會發現你的元件層級又多,巢狀又深,出問題的時候定位非常麻煩。

邏輯複用問題,這方面大家最熟悉的相關庫recompose, 但是作者本人已經加入react 團隊了而且還發了宣告

Hi! I created Recompose about three years ago. About a year after that, I joined the React team. Today, we announced a proposal for Hooks. Hooks solves all the problems I attempted to address with Recompose three years ago, and more on top of that. I will be discontinuing active maintenance of this package (excluding perhaps bugfixes or patches for compatibility with future React releases), and recommending that people use Hooks instead. Your existing code with Recompose will still work, just don't expect any new features. Thank you so, so much to @wuct and @istarkov for their heroic work maintaining Recompose over the last few years.

翻譯一下就是Hooks解決了我三年前嘗試用Recompose解決的所有問題,並且更多地解決了這個問題,並且停止維護這個庫。

生命週期

跟 vue 相比,react的各種生命週期實在是太多了。但是 hooks就是一個生命週期 useEffect,它是一個 ,你可以把它當成componentDidMount, componentDidUpdate, and componentWillUnmount 的集合。具體我會在下面篇幅介紹。

this的問題

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
    	count: 0,
    	count1: 0, 
    	count2: 0
    },
    this.setCount = this.setCount.bind(this);
  
  }
  setCount () {}
  setcout = () => {}
  
  render() {
   <button onClick={this.setCount}>測試0</button>
   <button onClick={this.setCount}>測試1</button>
  }
}

複製程式碼

就算用 bind 還是箭頭函式還是要小心this 的問題。但是在fp 程式設計方式裡面是沒有 this 概念的

準備工作

react >=16.8 react native >= 0.59
複製程式碼

常用 api

  • useState
  • useEffect
  • useReducer
  • useRef
  • useContext

下面逐一來介紹

const App = () => {
  const [loading, updateLoading] = useState(true)
  const [list, updateList] = useState([])
  const [title, updateTitle] = useState(undefined)
  useEffect(() => {
    getData()
  }, [])
  const getData = () => {
    fetch('https://api.douban.com/v2/movie/in_theaters?city=廣州&start=0&count=10')
    .then(response => response.json()
    )
    .then(res => {
      updateTitle(res.title)
      updateList(res.subjects)
      updateLoading(false)
    })
  }
  return (
    <View style={styles.container}>
      <Text style={styles.welcome}>{title}</Text>
      {loading ? <Text>載入中---</Text> : list.map(i => <Text key={i.id} style={styles.instructions}>{i.title}</Text>)}
    </View>
  )
}
複製程式碼

useState

 const [loading, updateLoading] = useState(true)
複製程式碼

先定義好state,陣列第一個值是你需要更新的值,第二個值是更新該值得函式,useState() 函式傳入的初始值。更新的時候呼叫 updateLoading(true)就行了

useEffect

通俗點就是 componentDidMount,componentDidUpdate、componentWillUnmount三個生命週期的合集。渲染後必然會觸發,怎麼理解呢?就是你通過 updateLoading 函式去更新 loading 的值,頁面重新 render ,這個時候這個方法就會被觸發。

問題來了我上面的程式碼豈不是會重複請求直到 ??? ?
useEffect 可以傳入第二個引數來避免效能的損耗,如果第二個引數陣列中的成員變數沒有變化則會跳過此次改變。傳入一個空陣列 ,那麼該 effect 只會在元件 mount 和 unmount 時期執行,一般來說傳個唯一的 id 是最佳作法。

如何做一些取消操作呢?

比如定時器,釋出訂閱,有時候元件解除安裝的時候要取消這個時候該怎麼辦呢 ? 可以return 一個新的函式

注意一個坑點

當我準備用定時器去模擬請求的時候發現一個問題?

function Counter() {
  let [count, setCount] = useState(0);
  useEffect(() => {
    let id = setInterval(() => {
      setCount(count + 1);
    }, 1000);
    return () => clearInterval(id);
  }, []);
  return <Tex>{count}</Text>;
}
複製程式碼

我發現我的數字是 1 不會動了!! why ?

問題在於,useEffect 在第一次渲染時獲取值為 0 的 count,我們不再重執行 effect,所以 setInterval 一直引用第一次渲染時的閉包 count 0,以至於 count + 1 一直是 1。經過不斷的實現,發現可以用新的 api 來規避問題。一種是通過 useRef, 一種是用過 useReducer

useReducer

useState的替代方案。接受型別為(state,action) => newState的reducer,並返回與dispatch方法配對的當前狀態。 (如果熟悉Redux,你已經知道它是如何工作的。) 用過 redux的相信對這個reducer 都不陌生 使用和redux 如出一撤。
? actions.js

export const loading = 'LOADING'
export const list = 'LIST'
export const updateLoading = (data) => ({
  type: loading,
  data
})
export const updateList = (data) => ({
  type: list,
  data
})
    
複製程式碼

? reducer.js

import {
  loading,
  list,
} from './actions'
export const initState = {
  loading: true,
  list: [],
}
export const initReducer = (state, {type, data}) => {
  switch (type) {
    case list:
      return {
        ...state,
        list: data
      }
    case loading:
      return {
        ...state,
        loading: data
      }
    default:
      return state
  }
}

複製程式碼

最後連線元件

import React, { useReducer, useEffect, useRef } from 'react';
import {Platform, StyleSheet, Text, View} from 'react-native';
import { updateLoading, updateList } from './store/actions'
import { initState, initReducer } from './store/reducers'

const App = () => {
  const [state, dispatch] = useReducer(initReducer, initState)
  useEffect(() => {
    getData()
  }, [])
  const getData = () => {
    fetch('https://api.douban.com/v2/movie/in_theaters?city=廣州&start=0&count=10')
    .then(response => response.json()
    )
    .then(res => {
      dispatch(updateList(res.subjects))
      dispatch(updateLoading(false))
    })
  }
  const {list = [], loading} = state 
  return (
    <View style={styles.container}>
      {loading ? <Text>載入中---</Text> : list.map(i => <Text key={i.id} style={styles.instructions}>{i.title}</Text>)}
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
});
export default App
複製程式碼

效果圖

在react native中使用hooks

useRef

這個沒什麼主要多了 current 這一層

import React, { useReducer, useEffect, useRef } from 'react';
import {Platform, StyleSheet, Text, View, TextInput } from 'react-native';
import { updateLoading, updateList } from './store/actions'
import { initState, initReducer } from './store/reducers'

const App = () => {
  const _ref = useRef()
  const [state, dispatch] = useReducer(initReducer, initState)
  useEffect(() => {
    getData()
  }, [])
  const getData = () => {
    fetch('https://api.douban.com/v2/movie/in_theaters?city=廣州&start=0&count=10')
    .then(response => response.json()
    )
    .then(res => {
      dispatch(updateList(res.subjects))
      dispatch(updateLoading(false))
    })
  }
  const {list = [], loading} = state 
  return (
    <View style={styles.container}>
      {loading ? <Text>載入中---</Text> : list.map(i => <Text key={i.id} style={styles.instructions}>{i.title}</Text>)}
      <TextInput ref={_ref} />
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
});
export default App
複製程式碼

在react native中使用hooks

自定義hooks

文章開頭說過複用問題,hooks 怎麼做的更好其實就是可以自定義hooks。比如我現在要封裝一個 請求hooks 可以這麼幹

import React, { useState, useEffect  } from 'react';
export const request = (initData) => {
  const [data, updateData] = useState(initData)
  const [url, updateUrl] = useState(null)
  const [isLoading, updateLoading] = useState(true)
  const [isError, updateError] = useState(false)
  useEffect(() => {
    fetchData()
  }, [url])
  const fetchData = () => {
    if(!url) return 
    updateLoading(true)
    try {
      fetch(url).then(res => res.json()).then(res => {
        updateData(res)
      })
    } catch (error) {
      updateError(error)
      console.log(error)
    }
    updateLoading(false)
  }
  const doFetch = url => {
    console.log(url, 'url')
    updateUrl(url)
  }
  return { data, isLoading, isError, doFetch }
}
複製程式碼

如何呼叫呢

  const { data, isLoading, doFetch } = request([])
  useEffect(() => {
    getData()
  }, [])
  const getData = async () => {
    const url = 'https://api.douban.com/v2/movie/in_theaters?city=廣州&start=0&count=10'
    doFetch(url)
  }
複製程式碼

結語

最開始出來測試版的時候,還在觀望。現在在 pc 上用了一段時間後,感覺真香,越來越喜歡hooks 。

相關文章