React Hooks入门指南

栏目: IOS · Android · 发布时间: 5年前

内容简介:React16.8.0+版本才支持Hooks;完全可选,可以只在部分函数组件里面应用Hooks,没必要完全应用Hooks到整个项目;完全向后兼容,没有重大改变;尚无计 划移除Class组件,下文会有过渡策略;Hooks没有新概念,也没有修改旧理念,只是一种更加简单直接的API,来对既有的props,state,context,refs, lifeCycle做强有力的组合;Hooks可以解决许多表面看似不相关的问题。React没有提供一个将可复用行为(比方说connect到store)粘贴到组件的方式。ren

React16.8.0+版本才支持Hooks;完全可选,可以只在部分函数组件里面应用Hooks,没必要完全应用Hooks到整个项目;完全向后兼容,没有重大改变;尚无计 划移除Class组件,下文会有过渡策略;Hooks没有新概念,也没有修改旧理念,只是一种更加简单直接的API,来对既有的props,state,context,refs, lifeCycle做强有力的组合;Hooks可以解决许多表面看似不相关的问题。

动机

难以复用stateFul logic

React没有提供一个将可复用行为(比方说connect到store)粘贴到组件的方式。render props和higher-order components可以解决这类问题。但是这些 模式需要对相关组件进行重构。有可能会略显笨重,较复杂的话很难继续改造。在React DevTools里面会发现组件的‘wrapper hell‘,有providers、 consumers、higher-order components、render props以及其他抽象层。

有了Hooks的话,我们可以从一个组件当中抽取出stateFul logic,单独测试并复用起来。这些都是Hooks定制化的基石。

复杂组件难以理解

我们发现一些组件一开始很简单,但是慢慢变得不易于维护,到处弥漫着stateFul logic、副作用。每个生命周期钩子方法经常混合着许多不相关逻辑。例如, 组件在componentDidMount和componentDidUpdate中请求数据,但是componentDidMount方法也有可能包含注册事件监听,在componentWillUnmount中 取消事件监听。一同变化的相关代码被拆分开来了,完全不相关的代码却在一个方法里结合起来了。这样很容易引入bug。

Classes既困惑了人又困惑了机器

就像Svelte、Angular、Glimmer那样,组件预编译会在未来释放巨大潜力。特别是不局限于模板,最近React团队在使用Prepack探索component folding:

function Foo (props) {
  if (props.data.type === 'img') {
    return <img src={props.data.src} className={props.className} alt={props.alt} />
  }
  return <span>{props.data.type}</span>
}
Foo.defaultProps = {
  alt: 'An image of Foo'
}

var CSSClasses = {
  bar: 'bar'
}
module.exports = CSSClasses;

var Foo = require('Foo')
var Classes = require('Classes')
function Bar (props) {
  return <Foo data={{ type: 'img', src: props.src }} className={Classes.bar} />
}
function Bar_optimized (props) {
  return <img src={props.src} className="bar" alt="An image of Foo." />
}
// All above is called `Dead-code Elimination`
复制代码

发现class组件会有潜在模式,使得component folding做出的优化回退到一个更慢的结果。classes也存在诸如这些问题:压缩不足,热加载分离成片状,不可靠。 我们想要推出一款API使得代码依然保持住component folding做出的优化。 是他是他就是他了,Hooks让你能够不用Classes却能使用上已有的React特性。React 组件总是贴近于函数。Hooks拥抱函数,但是不会牺牲React的精髓,并且 不要求你掌握复杂的函数式、响应式编程技巧。

过渡策略

Hooks和现有代码是并行关系,不提倡把现有class组件改写为hooks,特别是复杂的大组件,但是新组件可以采用Hooks来写^_^

Using the State Hook

import React, { useState } from 'react'

function Example () {
  const [count, setCount] = useState(0)

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  )
}

// equivalent to
class Example extend React.Component {
  constructor (props) {
    super(props)
    this.state = {
      count: 0
    }
  }

  render () {
    return (
      <div>
      <p>You clicked {count} times</p>
      <button onClick={() => this.setState({ count: this.state.count + 1 })}>Click me</button>
    </div>
    )
  }
}
复制代码

这时候我们再来解释一下React Hooks:一个Hook是一个能够 hook into React特性的特殊函数。 useState 是一个增加React state到函数组件的 Hook。

Using the effect hook

执行组件里的副作用,和class组件的生命周期方法相似。 useEffect Hook是 componentDidMount, componentDidUpdate, componentWillUnmount 的结合体

import React, { useState, useEffect } from 'react'

function Example () {
  const [count, setCount] = useState(0)
  // Similar to componentDidMount and componentDidUpdate
  useEffect(() => { // this arrow method is called the effect method, you can also name it rather than arrow function
  // using arrow function, if there's lot code, we should add remark, on the other side, we can use multiple useEffect
  // with named-effect-function if you like
    // Update the document title using the browser API
    document.title = `You clicked ${count} times`
  })
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  )
}

// equivalent to
class Example extend React.Component {
  constructor (props) {
    super(props)
    this.state = {
      count: 0
    }
  }
  componentDidMount () {
    document.title = `You clicked ${this.state.count} times`
  }
  // duplicate code in another lifeCycle method
  componentDidUpdate () {
    document.title = `You clicked ${this.state.count} times`
  }

  render () {
    return (
      <div>
      <p>You clicked {count} times</p>
      <button onClick={() => this.setState({ count: this.state.count + 1 })}>Click me</button>
    </div>
    )
  }
}
复制代码

React组件有两种副作用:一种不要求cleanup,另一种要求cleanup。

Effects Without Cleanup

有时候,我们想要在React更新DOM之后运行一些代码。网络请求、DOM手动操作、日志、以及其他的命令式API是常见的不要求cleanup的effects。我们说我们运行完后就立刻忘记这块 代码。 在class组件里, render 方法不会带来副作用,我们会在 componentDidMount, componentDidUpdate 当中放入副作用。可以看到上面的代码在两个生命 周期方法里重复了,因为我们想要的效果是不管组件是挂载完还是更新完都去执行同样的副作用。也就是说 我们想要每次渲染后都执行同样的某个逻辑 ,但是 React没有这样的方法,我们可以抽成一个独立方法但是仍然需要在两个生命周期方法里分别调用一次 useEffect可以读取函数组件内的state、props这一能力依赖于函数作用域。Hooks拥抱JavaScript闭包所以避免引入React特定API,因为闭包已经做到了, React Hooks原理可以参考github源码,以后我会抽时间整理成一篇博客。 如果你简单的理解每个useEffect都是在每次render之后执行的话,那你可能还不知道useEffect的第二个参数(影响因子数组)。 有经验的开发者可能注意到useEffect的第一个方法参数每次渲染后都会不一样。没错,这样我们就可以在 effect 内部读取到最新的 count 数据,每次渲染后 ,都会有一个不同的新effect方法替换之前的旧effect方法。某种程度上,这使得effects表现的更像render结果的一部分,每个effect属于一个render。

componentDidMount, componentDidUpdate 不同的是,effects不会阻止浏览器更新渲染帧。webApp响应性会更好。大部分effects不必同步发生。 在一些不常见的场景比如计算布局的时候,需要同步的effects。可以采用区别于 useEffectuseLayoutEffect API。

Effects With Cleanup

像事件的添加与销毁、subscribe && unsubscribe等等。

Rules of Hooks

不要在循环、条件或者嵌套语句中调用Hooks。

我们要在React函数组件内部最外层直接调用Hooks,这样,Hooks可以在每一次组件渲染的时候保持顺序一致,React也可以正确保存多次调用 useState useEffect 之间的Hooks状态。可以在React函数组件、custom Hooks内部调用Hooks,但是不要 在正常的JavaScript函数内部调用Hooks 。这样可以确保组件内部的 所有 stateFul logic 可读性强。如果写JavaScript而不是TypeScript的话可以 install eslint-plugin-react-hooks :

// Your eslint configuration
{
  "plugins": [
    // ...
    "react-hooks"
  ],
  "rules": {
    // ...
    "react-hooks/rules-of-hooks": "error", // Checks rules of hooks
    "react-hooks/exhaustive-deps": "warn" // Checks effect dependencies
  }
}
复制代码

现在我们来解释下为什么需要 把Hooks调用放在函数组件内部最外层:

function Form () {
  // 1. Use the name state variable
  const [ name, setName ] = useState('Mary')
  useEffect(function persistForm () {
    localStorage.setItem('formData', name)
  })
  const [ surname, setSurname ] = useState('Poppins')
  useEffect(function updateTitle () {
    document.title = `${name} ${surname}`
  })
}
// React怎么就知道哪个state是哪个useState调用出来的呢,其实依赖于Hooks的调用顺序。我们的例子可以起作用的原因在于每次渲染时Hooks的调用顺序
// 保持一致!
// First Render
useState('Mary') // 1. Initialize the name state variable with 'Mary'
useEffect(persistForm) // 2. Add an effect for persisting the form
useState('Poppins') // 3. Initialize the surname state
// Second Render
useState('Mary') // 1. Read the name state variable (argument is ignored)
useEffect(persistForm) // 2. Replace the effect for persisting the form
useState('Poppins') // 3. Read the surname state variable(argument is ignored)
useEffect(updateTitle) // 4. Replace the effect for updating the title
复制代码

只要每次渲染的Hooks调用顺序保持一致,React就可以把一些本地state数据和每次调用关联起来。但是把一个Hook调用放入一个condition的话:

if (name !== '') {
  useEffect(function persistForm () {
    localStorage.setItem('formData', name)
  })
}
复制代码

第一次渲染时 name !== '' 条件是 true ,执行了这条Hook,但是,下次渲染时用户有可能清除了表单,条件值为 false ,跳过了这条Hook,Hooks调用顺序 改变了:

useState('Mary') // 1. Read the name state variable (argument is ignored)
// useEffect(persistForm) // This Hook was skipped!
useState('Poppins') // 2(but was 3). Failed to read the surname state variable
useEffect(updateTitle) // 3(but was 4). Failed to replace
复制代码

React在第二次 useState 调用时并不知道要return什么。React期待组件内第二个Hook调用是 persistForm effect,就像上一次render时那样,然而并 不是。我们跳过的Hook后面的每个Hook都会迁移一位,引来bug。 如果我们想条件性地执行一个effect,我们要把条件放到Hook内部:

useEffect(function persistForm () {
  if (name !== '') {
    localStorage.setItem('formData', name)
  }
})
复制代码

Custom Hooks

构建自己的Hooks,使得分离组件逻辑到可复用地函数组件去。 之前我们学习Effect Hook时,看到用一条信息表明朋友是否在线的聊天应用:

import React, { useState, useEffect } from 'react'

function FriendStatus (props) {
  const [ isOnline, setIsOnline ] = useState(null)

  useEffect(() => {
    function handleStatusChange (status) {
      setIsOnline(status.isOnline)
    }

    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange)
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange)
    }
  })

  if (isOnline === null) return 'Loading...'
  return `O${isOnline ? 'n' : 'ff'}line`
}
复制代码

现在应用要增加联系人列表,而且需要展示绿色在线状态的许多姓名,我们可以吧相似逻辑复制到我们的 FriendListItem 组件但是不够理想:

import React, { useState, useEffect } from 'react'

function FriendListItem(props) {
  const [isOnline, setIsOnline] = useState(null)

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline)
    }

    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange)
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange)
    }
  })

  return (
    <li style={{ color: isOnline ? 'green' : 'black' }}>
      {props.friend.name}
    </li>
  )
}
复制代码

换句话说,如何复用 FriendStatus FriendListItem 的逻辑呢。以前针对class组件,我们有 render props, higher-order components 两个手段来复用组件内部 逻辑。对于Hooks的话,又该如何在不增加组件树层级的情况下解决同样的问题呢。方案就是:

抽离出一个自定义Hook

在JavaScript函数间复用逻辑的办法是剥离出另一个函数,函数组件和Hooks都是函数,所以同理喽! 一个自定义Hook是一个以“use“打头的JavaScript函数,它调用了其他Hooks:

import React, { useState, useEffect } from 'react'

function useFriendStatus (friendID) {
  const [ isOnline, setIsOnline ] = useState(null)

  useEffect(() => {
    function handleStatusChange (status) {
      setIsOnline(status.isOnline)
    }

    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange)
    return () => {
      ChatAPI.unsubscribeToFriendStatus(friendID, handleStatusChange)
    }
  })

  return isOnline
}
复制代码

和React 组件不一样的是,一个自定义Hook不用特定签名。我们自行决定参数,返回值,和正常函数一样,唯一不同的就是以 use 开头,这样就能方便应用 rules of Hooks 。 现在看看如何使用自定义Hook:

function FriendStatus (props) {
  const isOnline = useFiendStatus(props.friend.id)

  if (isOnline === null) return 'Loading'

  return `O${isOnline ? 'n' : 'ff'}line`
}

function FriendListItem (props) {
  const isOnline = useFiendStatus(props.friend.id)

  return (
    <li style={{ color: isOnline ? 'green' : 'black' }}>
      {prop.friend.name}
    </li>
  )
}
复制代码

我们约定了自定义Hooks必须yi use 开头,非常重要,否则我们无法检测Hooks规则,因为无法判断一个函数组件内部是否包含Hooks。 使用相同Hook的两个函数组件共享状态吗?No,自定义Hooks时复用组件本地逻辑的机制,比如发起订阅、记住当前值,但是每次使用一个自定义Hook的时候,它的所 有state和effects都是独立的。 一个自定义的Hook如何获取独立的state?一个Hook的每次调用都有独立的状态。因为我们是直接调用了 useFriendStatus ,React认为我们的组件仅仅调用了 useState useEffect

释放你的想象力

自定义Hooks提供了React组件前所未有的复用逻辑地灵活性。我们可以处理表单、动画制作、声明式订阅、定时器、等等。 不要过早增加抽象。既然函数组件可以做很多,那么一不小心就会增加函数长度,这很正常,但是不用觉得立刻写成Hooks。

如果更新逻辑复杂或者数据需要聚合的话(比方说表单),可以考虑用 useReducer 代替 useState ,也用到了redux的思想,不亦乐乎。

Hooks API

基础Hooks

useState

const [ state, setState ] = useState(initialState)
复制代码

首次渲染时,返回的state就是 initialStatesetState 是用来更新state的方法,参数是新的state值,enqueue进组件重新渲染的队列。

如果新state时前一个state计算出来的,可以给 setState 传递一个函数,该函数接收前一个state作为参数,然后return新的state:

function Counter (initialCount) {
  const [ count, setCount ] = useState(initialCount)
  return (
    <>
      Count: {count}
      <button onClick={() => setCount(initialCount)}>Reset</button>
      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
    </>
  )
}
复制代码

和class组件 setState 不同的是, useState 不会自动合并更新对象。但是我们可以采用ES6扩展符+函数更新形式来做:

setState(prevState => ({ ...prevState, ...updatedValues }))
复制代码

另外就是可以采用 useReducer ,更适合包含多个子值的state对象。

如果初始值 initialState 是复杂计算的结果呢:

const [ state, setState ] = useState(() => {
  const initialState = someExpensiveComputation(props)
  return initialState
})
复制代码

React采用 Object.is 来比较前后state,以此来决定是否更新children或者触发effects。

React有可能在bail out(前后state一致,不会重新渲染)之前仍然再次渲染特定组件。我们不必在乎那么多,因为React不会进入到更深层级。 当然如果你在渲染时需要做复杂的计算,可以用 useMemo 来优化。

useEffect

useEffect(didUpdate)

didUpdate 是包含声明式、或者副作用的方法,比如订阅、订阅、定时器、日志、等等副作用代码允许直接置于React函数组件内部( render phase ),要 写副作用代码的话就用 useEffect 吧。

清除副作用

副作用经常搞出一些需要在组件卸载之前清除的资源,比如说订阅、定时器ID。这时候我们可以让 useEffect return一个清除器函数,来避免内存泄露:

useEffect(() => {
  const subscription = props.source.subscribe()
  return () => {
    subscription.unsubscribe()
  }
})
复制代码

另外, 前一个effect会在执行下一个effect之前被清除掉 。我们的例子中,每次更新时都会有一次新订阅。那么怎么避免每次更新时触发一次effect呢。

不像 componentDidMount componentDidUpdate ,传递给 useEffect 的方法会在layout、paint后触发,在一次延迟事件中。这对于大多数像订阅、事件 处理器这样的副作用都比较合适,因为大多数都不会阻塞浏览器更新帧。

但是,不是所有的副作用都会被推迟。比如对于用户可见的DOM修改必须在下一次重绘之前同步触发,这样用户就不会感觉到卡顿。对于这类effects,React提供 一个叫做 useLayoutEffect 的Hook。和 useEffect 一个样子,只是触发时机不一致。

尽管 useEffect 被推迟到浏览器重绘之后,却能在每次渲染之前触发。React总会在一次更新之前把上一次渲染的effects清除掉。

有条件地触发一次effect

拿上面的订阅器例子来说,每次更新时,我们其实可以只在source改变时去重新订阅:

// 给useEffect传递第二个数组参数,只有当数组里面的变量变化时才去触发该effect
useEffect(() => {
  const subscription = props.source.subscribe()
  return () => {
    subscription.unsubscribe()
  }
}, [props.source])
复制代码

需要注意的是,要确认数组元素是完备的,所有能决定该effect发生与否的变量都需列出。要不然有可能会错误地引用旧渲染中的值。

如果你想执行一次effect,并且也只清除一次( mount unmount ),那么可以传递空数组 [] 作为第二个参数。React知道此effect不依赖任何props、state 数据,所以不会重新执行。

我们推荐使用 eslint-plugin-react-hooks 的时候使用 exhaustive-deps 规则,依赖没有正确指定时会有警告,并告知如何修复。

每一个effect方法内部引用的值都应该在依赖列表中出现。不久的将来,一个高级编译器能够自动构建该数组。

useContext

const value = useContext(MyContext) // Accepts a context object(the value returned from React.createContext)
复制代码

一个调用 useContext 的组件总会在context变动时重新渲染。如果重新渲染组件很耗性能,那么请使用 useMemo

如果你之前熟悉context API的话,应该懂得 useContext(MyContext) 等同于类组件的 static contextType = MyContext 或者 <MyContext.Consumer> useContext(MyContext) 只会让你读取context,并且订阅它的变动。你仍然在组件树中需要 <MyContext.Provider> ,来为此context提供值

高级Hooks

下面的Hooks要么是基础hooks的变体,要么是特定场合下的的需求。不用担心提前学习没用的东西。

useReducer

const [state, dispatch] = useReducer(reducer, initialState)
// or
const [state, dispatch] = useReducer(reducer, initialArg, init) // 初始state会被设置成 init(initialArg)

// 你可以把计算初始state的逻辑抽离到reducer之外,这样也有利于初始化state的action操作:
function init (initialCount) {
  return { count: initialCount }
}

function reducer (state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 }
    case 'decrement':
      return { count: state.count - 1 }
    case 'reset':
      return init(action.payload)
    default:
      throw new Error()
  }
}

function Counter ({ initialCount }) {
  const [ state, dispatch ] = useReducer(reducer, initialCount, init)
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({ type: 'reset', payload: initialCount })}>
        Reset
      </button>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </>
  )
}
复制代码

useCallback

const memoizedCb = useCallback(
  () => {
    doSomething(a, b)
  },
  [ a, b ],
)
复制代码

作用类似于 shouldComponentUpdate

useCallback(fn, deps) 等同于 useMemo(() => fn, deps)

useMemo

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [ a, b ])
复制代码

useRef

const refContainer = useRef(initialVal)
复制代码

useRef 返回一个可改变的ref对象, .current 属性初始化成 initialValue 参数。返回的对象会在组件的生命周期内始终存留。 比如说要命令式地访问子组件:

function TextInputWithFocusButton () {
  const inputEl = useRef(null)
  const onBtnClick = () => {
    // `current` 指向挂载了的输入框元素
    inputEl.current.focus()
  }
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onBtnClick}>Focus the input</button>
    </>
  )
}
复制代码

但是 useRef()ref 属性更有用。就像class实例属性一样方便。

useRef 不会在内容变动时通知你,改变 .current 属性不会引发重新渲染。如果你想要在React挂载或者卸载某个DOM节点的ref时执行某些代码的话,采用callback ref吧.

useImperativeHandle

useImperativeHandle(
  ref,
  () => {
    handler
  }, // createHandle
  [input], // deps
)
复制代码

useImperativeHandle 自定义了使用 ref 时暴露给父组件的实例值。正常情况下,使用refs的命令式代码应该避免。 userImperativeHandle 应该和 forwardRef 一起使用。

function FancyInput (props, ref) {
  const inputRef = useRef()
  useImperatieHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus()
    },
  }))
  return <input ref={inputRef} ... />
}
FancyInput = forwardRef(FancyInput)
复制代码

一个渲染 <FancyInput ref={FancyInputRef} /> 的父组件,能够调用 fancyInputRef.current.focus()

useLayoutEffect形式类似 useEffect ,但是在所有DOM操作之后立刻触发。用它去从DOM中读取layout然后重新渲染。

useLayoutEffectcomponentDidMount componentDidUpdate 触发阶段一致。我们推荐尽量多用 useEffect ,只有遇到问题才去尝试 useLayoutEffect

服务端渲染的话, useLayoutEffect useEffect 都是在JavaScript下载后才去执行,所以React会对包含 useLayoutEffect 的服务端渲染组件发出:warning:。 有两个修复手段:把逻辑搬进 useEffect (如果第一次渲染没有必要);推迟组件显示到客户端渲染(如果直到 useLayoutEffect 执行后HTML才显得完整)。

为了从服务端渲染HTML中剔除需要layout effects的组件,采用条件渲染: showChild && <Child /> 并且推迟显示: useEffect(() => { setShowChild(true) }, []) 。这样的话UI就不会有broken现象。

useDebugValue

useDebugValue(value)
复制代码

用于在React开发者 工具 中显示自定义hooks的标签:

function useFriendStatus (friendID) {
  const [ isOnline, setIsOnline ] = useState(null)

  // ...

  // Show a label in DevTools next to this Hook
  // e.g. "FriendStatus: Online"
  useDebugValue(isOnline ? 'Online' : 'Offline')

  return isOnline
}
复制代码

Hooks FAQ

Hooks比class组件:+1|type_1_2:在哪里

这里有一篇Dan Abramov在medium上面发表的文章。

Hooks可以完全充当class组件嘛

目标是尽快吧,但是 getSnapshotBeforeUpdate componentDidCatch 这两个钩子暂时还没有覆盖到,但是处于计划当中。

Hooks可以替换render props和高阶组件嘛

经常吧,render props和高阶组件都只渲染一个子组件。我们把Hooks看作一个简洁之法。但是render props、高阶组件还是有额外用武之地: 一个有 renderItem 属性的虚拟滚动条组件、包含特定DOM结构的虚拟容器组件。但是大多数情况下,Hooks足够有用,可以减少组件树深度。

Hooks影响现有的Redux connect()、React Router嘛

不会。 不过在将来,这些库的新版本有可能会有一些自定义Hooks: useRedux() useRouter() 这样的,就用不上容器组件了。

静态类型?

基于函数,你说呢。比起HOC,TypeScript完全拥抱React Hooks。

生命周期钩子怎么映射到Hooks

  • constructor : 函数组件不需要构造器,可以用useState初始化state。
  • getDerivedStateFromProps : 可以尝试在render时更新
  • shouldComponentUpdate : 用 React.memo 来实现
  • render : 函数体本身
  • componentDidMount, componentDidUpdate, componentWillUnmount : useEffect 都可以做到
  • componentDidCatch, getDerivedStateFromError : 暂不支持,以后会增加相应Hook

如何处理数据请求

这里有个codesandbox的Demo, 还有这篇文章讲得很全。

有实例属性这样的东西吗

useRef() 不仅仅是DOM引用。 ref 对象是一个带有 current 属性的通用容器,他可以被修改,容纳任何值,就有一个类的实例属性:

function Timer () {
  const intervalRef = useRef()

  useEffect(() => {
    const id = setInterval(() => {
      // ...
    })
    intervalRef.current = id
    return () => {
      clearInterval(intervalRef.current)
    }
  })
}
复制代码

怎么获取旧props、state

function Counter () {
  const [ count, setCount ] = useState(0)

  const prevCountRef = useRef()
  useEffect(() => {
    prevCountRef.current = count
  })
  const prevCount = prevCountRef.current

  return <h1>Now: {count}, before: {prevCount}</h1>
}
复制代码

我怎么测量一个DOM

为了测量DOM节点的尺寸、大小。用 callback ref 吧:

function MeasureExample () {
  const [ height, setHeight ] = useState(0)

  const measuredRef = useCallback(node => {
    if (node !== null) setHeight(node.getBoundingClientRect().height)
  }, [])

  return (
    <>
      <h1 ref={measuredRef}>Hello, world</h1>
      <h2>The above header is {Math.round(height)}px tall</h2>
    </>
  )
}
复制代码

上面例子中我们为什么不用 useRef 呢,因为一个ref对象不会在ref值变动的时候发出通知。采用回调ref可以做到子组件也可以显示测量好的node节点。

另外还有许许多多的Hooks FAQ,大家可以多看看React官网FAQ。

Hooks 相关的博客、资源我找了一些挺不错的:

star数上千的github项目却并不多,博客比较少; 我能找到比较好的资源有(大侠看到好的资源可以留个言?):

  • Github awesome-react-hooks 里面有一些比较综合性的资源,推荐的文档、博客教程、视频教程 播客、工具、npm package都有(上千的却没有几个)
  • React Hooks fetch Data 关于useEffect处理数据请求的文章,建议看前半部分就好了,后半部分没什么用
  • React Hooks RFC 里面的idea比较多,有一些不错的idea,大致看那些高赞的就行
  • From React Component to Hooks 了解到了useMount, 不过我不赞同这种用一个个Hook来代替以前的一个个class lifecycle,这没有任何好处并且回归旧思想,相反我们可以多用一些useCallback、useMemo、useRef等等这些高级 Hooks来实现我们的功能。
  • Dan Abramov tell you sth about React Hooks 哈哈哈, 里面确实有Dan的独到见解,但是去年十月份发表的一篇文章我能说更多的是举了一些动图例子告诉你Hooks有多么厉害的感觉嘛、干货并不多哈
  • Dan personal post overreacted.io about useEffect Dan的个人博客聚集地 overreacted.io上的一篇文章,干货挺多的,不过拒绝评论哈哈哈,静态网站搭起来的哈哈哈
  • github react-use 挺多自定义Hooks,还有传感器API相关的哈哈哈,还有其他类别的自定义Hooks。star数有6000多了,666.
  • React Hooks Share of Li Zixiang on FDCon 2019 今年携程李子翔5月份在FDCon 的一篇有关React Hooks的分享,蛮不错的。

总结一下,我们应该花更多的精力多看官网对其原理机制、使用的阐述,还有多研究一下高级hooks、自定义hooks;可以贡献到github或者写成博客,这样hooks社区就会好很多, 另外有空可以看看源码。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Docker——容器与容器云(第2版)

Docker——容器与容器云(第2版)

浙江大学SEL实验室 / 人民邮电出版社 / 2016-10 / 89.00元

本书根据Docker 1.10版和Kubernetes 1.2版对第1版进行了全面更新,从实践者的角度出发,以Docker和Kubernetes为重点,沿着“基本用法介绍”到“核心原理解读”到“高级实践技巧”的思路,一本书讲透当前主流的容器和容器云技术,有助于读者在实际场景中利用Docker容器和容器云解决问题并启发新的思考。全书包括两部分,第一部分深入解读Docker容器技术,包括Docker架......一起来看看 《Docker——容器与容器云(第2版)》 这本书的介绍吧!

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器