hook不是正常的函数,使用hook需要遵循一定的规则

  • Only Call Hooks at the Top Level
    • Don’t call Hooks inside loops, conditions, or nested functions.
  • Only Call Hooks from React Functions
    • Don’t call Hooks from regular JavaScript functions. Instead, you can:
      • Call Hooks from React function components.
      • Call Hooks from custom Hooks

create-react-app默认为Eslint安装了eslint-plugin-react-hooks插件,用于检测上述规则

custom hooks

自定义hook是常规的JS函数,只需要遵循hook规则,且必须以use开头
自定义Hook显然不仅是一种可重用的工具,它们还为将代码划分为更小的模块化部分提供了一种更好的方式。

在下面的App组件中多次使用useState, 都具有相同的解构

  1. import React, { useState } from 'react'
  2. const App = () => {
  3. const [name, setName] = useState('')
  4. const [born, setBorn] = useState('')
  5. const [height, setHeight] = useState('')
  6. return (
  7. <div>
  8. <form>
  9. name:
  10. <input
  11. type="text"
  12. value={name}
  13. onChange={(event) => setName(event.target.value)}
  14. />
  15. <br />
  16. birthdate:
  17. <input
  18. type="date"
  19. value={born}
  20. onChange={(event) => setBorn(event.target.value)}
  21. />
  22. <br />
  23. height:
  24. <input
  25. type="number"
  26. value={height}
  27. onChange={(event) => setHeight(event.target.value)}
  28. />
  29. </form>
  30. <div>
  31. {name} {born} {height}
  32. </div>
  33. </div>
  34. )
  35. }
  36. export default App

抽离成自定义hook,优点是可以重用

  1. import React, { useState } from 'react'
  2. // 自定义hook
  3. const useField = (type) => {
  4. const [value, setValue] = useState('')
  5. const onChange = (event) => {
  6. setValue(event.target.value)
  7. }
  8. return {
  9. type,
  10. value,
  11. onChange,
  12. }
  13. }
  14. const App = () => {
  15. const name = useField('text')
  16. const born = useField('date')
  17. const height = useField('number')
  18. return (
  19. <div>
  20. <form>
  21. name:
  22. <input type={name.type} value={name.value} onChange={name.onChange} />
  23. <br />
  24. birthdate:
  25. <input type={born.type} value={born.value} onChange={born.onChange} />
  26. <br />
  27. height:
  28. <input
  29. type={height.type}
  30. value={height.value}
  31. onChange={height.onChange}
  32. />
  33. </form>
  34. <div>
  35. {name.value} {born.value} {height.value}
  36. </div>
  37. </div>
  38. )
  39. }
  40. export default App

因为name对象的属性名与input期望的props属性名一一对应,因此可以使用展开属性

  1. <form>
  2. name: <input {...name} />
  3. <br />
  4. birthdate: <input {...born} />
  5. <br />
  6. height: <input {...height} />
  7. </form>

input元素的type属性有多少种?
互联网上开始充斥着越来越多关于Hook的有用资料。 如下是值得一查的资料来源:

Exercises

将自定义hook写在**src/hooks/index.js**文件里, 使用具名导出

  1. import { useState } from 'react'
  2. export const useField = (type) => {
  3. const [value, setValue] = useState('')
  4. const onChange = (event) => setValue(event.target.value)
  5. return {
  6. type,
  7. value,
  8. onChange,
  9. }
  10. }

导入

  1. import { useField } from './hooks'

Reset

想用reset元素清空表单,发现没有效果,这是因为reset原理是把表单还原成默认的value值,而不是清空,
如果默认的value值不为空,则无法达到清空的目的

  1. <form>
  2. <input type="text" value="xxx"/>
  3. <input type="password" value="hahah"/>
  4. <input type="reset" /> <!-- 清空无效 -->
  5. </form>

点击form中的button触发submit

解决办法1:给button点击事件添加**event.preventDefault()**阻止默认事件,或者添加**event.stopPropagation()**阻止冒泡
解决办法2:给button加上属性**type="button"**

如果展开运算有冲突,将要展开的元素单独放一个对象

  1. import { useState } from 'react'
  2. export const useField = (type, name) => {
  3. const [value, setValue] = useState('')
  4. const onChange = (event) => setValue(event.target.value)
  5. const reset = () => setValue('')
  6. return {
  7. props: {
  8. type,
  9. name,
  10. value,
  11. onChange,
  12. },
  13. reset,
  14. }
  15. }
  1. const content = useField('text')
  2. // ...
  3. <div>
  4. content
  5. <input {...content.props} />
  6. </div>

exercise 7.7

使用api获取信息,使用catch处理错误

  1. const useCountry = (name) => {
  2. const [country, setCountry] = useState(null)
  3. useEffect(() => {
  4. if (name) {
  5. const queryUrl = `https://restcountries.eu/rest/v2/name/${name}?fullText=true`
  6. axios
  7. .get(queryUrl)
  8. .then((response) => {
  9. setCountry({ found: true, data: response.data[0] })
  10. })
  11. .catch(() => {
  12. setCountry({ found: false })
  13. })
  14. }
  15. }, [name])
  16. return country
  17. }

exercise 7.8

将与后端通信写到自定义hook中

  1. import { useEffect, useState } from 'react'
  2. import axios from 'axios'
  3. export const useField = (type) => {
  4. const [value, setValue] = useState('')
  5. const onChange = (event) => {
  6. setValue(event.target.value)
  7. }
  8. return {
  9. type,
  10. value,
  11. onChange,
  12. }
  13. }
  14. export const useResource = (baseUrl) => {
  15. const [resources, setResources] = useState([])
  16. useEffect(() => {
  17. axios
  18. .get(baseUrl)
  19. .then((response) => {
  20. setResources(response.data)
  21. })
  22. .catch((error) => console.log(error))
  23. }, [baseUrl])
  24. const create = (resource) => {
  25. axios
  26. .post(baseUrl, resource)
  27. .then((response) => setResources(resources.concat(response.data)))
  28. .catch((error) => console.log(error))
  29. }
  30. const service = {
  31. create,
  32. }
  33. return [resources, service]
  34. }