如何重构 React 组件以使用 Hooks

重构现有应用程序或组件以使用 React Hooks 会带来一系列独特的挑战。在本文中,我们将介绍适用于各种应用程序类型的重构的一些通用问题,从基本问题开始,然后再讨论更高级的用例。

我们将介绍以下内容:

  • 将类组件转换为函数组件

    • 没有状态或生命周期方法的类组件

    • 具有道具、默认道具值和propType声明的类组件

    • 具有状态的类组件:单个或几个多个键

  • 权衡采用增量 Hooks

  • 简化生命周期方法

  • 比较useEffect对象值

    • 利用JSON.stringify

    • 使用手动条件检查

    • 使用useMemo钩子

  • 修复由于以下原因而中断的测试useEffect

  • 一种更安全的重构渲染道具 API 的方法

  • 处理状态初始化器

要继续阅读本文,您应该对React Hooks 的工作原理有所了解。让我们开始吧!

将类组件转换为函数组件

当您着手重构您的应用程序以使用 React Hooks 时,您将面临的第一个问题恰好是其他挑战的根源:您如何在不破坏任何功能的情况下将您的类组件重构为功能组件?

让我们看一下您将遇到的一些最常见的用例,从最简单的开始。

没有状态或生命周期方法的类组件

对于高级开发人员,上面的 gif 可以提供足够的上下文来发现从类到函数组件的重构差异。让我们详细探讨一下;下面的代码展示了你将拥有的最基本的用例,一个只呈现一些 JSX 的类组件:

// before
import React, {Component} from 'react';
​
class App extends Component {
  handleClick = () => {
    console.log("helloooooo")
  }
​
  render() {
    return (
      <div> 
        Hello World 
        <button onClick={this.handleClick}>
          Click me! 
        </button>
      </div> 
    )
  }
}
​
export default App

重构这个组件非常简单:

// after 
import React from 'react'
​
function App() {
  const handleClick = () => {
    console.log("helloooooo")
  }
​
  return (
    <div> 
      Hello World 
      <button onClick={handleClick}> Click me! </button>
    </div> 
  ) 
}
​
export default App

在上面的代码中,我们将class关键字替换为 JavaScript 函数。我们没有使用render()函数,而是直接通过父App()函数返回,它是一个组件。最后,在我们的函数组件中,我们不使用this. 相反,我们将其替换为函数范围内的 JavaScript 值。

具有道具、默认道具值和propType声明的类组件

类组件是另一个没有太多开销的简单用例:

// before
class App extends Component {
  static propTypes = {
    name: PropTypes.string
  }
  static defaultProps = {
    name: "Hooks"
  }
​
  handleClick = () => {
    console.log("helloooooo")
  }
​
  render() {
    return <div> 
      Hello {this.props.name} 
      <button onClick={this.handleClick}> Click me! </button>
    </div>    
  }
}

重构后,我们有以下代码:

function App({name = "Hooks"}) {
  const handleClick = () => {
    console.log("helloooooo")
  }
​
  return <div> 
      Hello {name} 
      <button onClick={handleClick}>Click me! </button>
    </div>
}
​
App.propTypes = {
  name: PropTypes.number
}

如您所见,该组件作为功能组件看起来要简单得多。组件函数的propsbecome 函数参数,默认 props 通过 ES6 默认参数语法处理,并static propTypes替换为App.propTypes.

具有状态的类组件:单个或几个多个键

当您拥有一个具有实际状态对象的类组件时,该场景会变得更加有趣。您的许多类组件将属于此类别或此类别的稍微复杂的版本。

考虑以下类组件:

class App extends Component {
  state = {
    age: 19
  }
​
  handleClick = () => {
    this.setState((prevState) => ({age: prevState.age + 1}))
  }
​
  render() {
    return <div> 
      Today I am {this.state.age} Years of Age 
      <div> 
        <button onClick={this.handleClick}>Get older! </button>
      </div>
    </div>
  }
}

该组件仅跟踪状态对象中的单个属性。够简单!

我们可以重构这段代码以使用useStateHook,它用于管理 React 中的状态,如下所示。请注意我们如何将 state 的默认值作为 的参数传递useState():

function App() {
  const [age, setAge] = useState(19);
  const handleClick = () => setAge(age + 1)
​
  return <div> 
      Today I am {age} Years of Age 
      <div> 
        <button onClick={handleClick}>Get older! </button>
      </div>
   </div>
}

这看起来简单多了!

如果这个组件有更多的状态对象属性,你可以使用多个useState调用,如下所示:

function App() {
  const [age, setAge] = useState(19);
  const [status, setStatus] = useState('married')
  const [siblings, setSiblings] = useState(10)
​
​
  const handleClick = () => setAge(age + 1)
​
  return (
   <div> 
      Today I am {age} Years of Age 
      <div> 
        <button onClick={handleClick}>Get older! </button>
      </div>
   </div>
  )
}

您可以在 中创建对象useState(),但是管理该状态将相当困难,导致所有字段在屏幕上重新呈现。尽管此示例相当简单,但我建议您查看本指南以获取更多示例。

权衡采用增量 Hooks

虽然重写您的应用程序和组件以使用 Hooks 听起来很棒,但它确实是有代价的,时间和人力是先行者。

如果您正在处理大型代码库,则可能需要在采用 Hooks 的早期阶段进行一些权衡。作为示例场景,让我们考虑以下组件:

const API_URL = "https://api.myjson.com/bins/19enqe";
​
class App extends Component {
  state = {
    data: null,
    error: null,
    loaded: false,
    fetching: false,
  }
​
  async componentDidMount() {
    const response = await fetch(API_URL)
    const { data, status } = {
      data: await response.json(),
      status: response.status
    }
​
    // error? 
    if (status !== 200) {
      return this.setState({
        data,
        error: true,
        loaded: true,
        fetching: false,
      })
    }
​
    // no error 
    this.setState({
      data,
      error: null,
      loaded: true,
      fetching: false,
    })
  }
​
  render() {
    const { error, data } = this.state;
​
    return error ? <div> "Sorry, an error occurred :(" </div> :
      <pre>{JSON.stringify(data, null, ' ')}</pre>
  }
}

上面的组件在挂载时向远程服务器发出请求以获取一些数据,然后根据结果设置状态。与其关注异步逻辑,不如关注setState调用:

class App extends Component {
 ... 

  async componentDidMount() {
    ...

    if (status !== 200) {
      this.setState({
        data,
        error: true,
        loaded: true,
        fetching: false,
      })
    }


    this.setState({
      data,
      error: null,
      loaded: true,
      fetching: false,
    })
  }

  render() {
    ...
  }
}

这些setState调用接收一个具有四个属性的对象。虽然这只是一个示例,但一般情况是您的组件setState使用大量对象属性进行调用。

使用 React Hooks,您可能会继续将每个对象值拆分为单独的useState调用。您可以使用带有 的对象useState,但这些属性是不相关的,并且在此处使用对象可能会使以后将代码分解为独立的自定义 Hook 变得更加困难。


超过 20 万开发人员使用 LogRocket 来创造更好的数字体验了解更多 →


因此,重构可能类似于以下代码:

... 
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  const [loaded, setLoading] = useState(false);
  const [fetching, setFetching] = useState(false);
...

您还必须将 this.setState调用更改为如下所示:

// no more this.setState calls - use updater functions. 
 setData(data);
  setError(null);
  setLoading(true);
  fetching(false);

虽然这可行,但如果您在组件中有很多setState调用,那么您最终将多次编写此代码或将它们分组到另一个自定义 Hook 中。

如果您想在代码库中实现对 Hooks 的增量采用,而代码更改较少且setState签名略有相似,该怎么办?在这种情况下,您必须做出权衡;在这里,我们将介绍useReducerHook。 useReducer具有以下签名:

const [state, dispatch] = useReducer(reducer)

reducer是一个接受状态和动作并返回 a 的函数newState:

const [state, dispatch] = useReducer((state, action) => newState)

从 reducer 返回的newState数据然后通过state变量被组件消耗。

如果你之前使用过 Redux,那么你就知道你action的对象一定是具有特定type属性的。但是,情况并非如此useReducer。相反,该reducer函数接受state和 some action,然后返回一个新的状态对象。我们可以利用这一点进行不那么痛苦的重构,如下所示:

... 
function AppHooks() {
  ... 
 const [state, setState] = useReducer((state, newState) => (
{...state, ...newState}
  ));

 setState({
    data,
    error: null,
    loaded: true,
    fetching: false,
  })
}

我们没有更改组件中各处的大部分this.setState调用,而是选择了一种更简单的增量方法,该方法不涉及大量代码更改。

不用this.setState({data, error: null, loaded: null,fetching: false}), 和 Hooks,你可以只删除this.,setState调用仍然有效。下面的代码使这成为可能:

const [state, setState] = useReducer((state, newState) => (
{ ...state, ...newState }
));

当您尝试更新状态时,传入的任何内容setState(通常称为dispatch)都会作为第二个参数传递给 reducer。我们称之为newState.

不像在 Redux 中那样实现复杂的switch语句,我们只是返回一个新的状态对象,它用传入的新值覆盖先前的状态。这类似于setState工作原理,更新状态属性而不是替换整个对象。

使用此解决方案,可以更轻松地在您的代码库中采用增量 Hooks,无需大量代码更改且具有类似的setState签名。以下是代码更改较少的完整重构代码:

function AppHooks() {
  const initialState = {
    data: null,
    error: null,
    loaded: false,
    fetching: false,
  }
  const reducer = (state, newState) => ({ ...state, ...newState })
  const [state, setState] = useReducer(reducer, initialState);

  async function fetchData() {
    const response = await fetch(API_URL);
    const { data, status } = {
      data: await response.json(),
      status: response.status
    }

    // error? 
    if (status !== 200) {
      setState({
        data,
        error: true,
        loaded: true,
        fetching: false,
      })
    }

    // no error 
    setState({
      data,
      error: null,
      loaded: true,
      fetching: false,
    })
  }

  useEffect(() => {
    fetchData()
  }, [])


  const { error, data } = state
  return error ?  Sorry, and error occured :(  :
    <pre>{JSON.stringify(data, null, ' ')}</pre>
}

简化生命周期方法

您将面临的另一个常见挑战是重构组件的 、 和生命周期方法中componentDidMount的componentWillUnmount逻辑componentDidUpdate。

useEffectHook 是提取这种逻辑的完美场所。默认情况下,其中的效果函数useEffect将在每次渲染后运行。如果你熟悉 Hooks,这是常识:

import { useEffect } from 'react'
useEffect(() => {
   // your logic goes here
   // optional: return a function for canceling subscriptions 
   return () => {}
})

Hook的一个有趣特性useEffect是您可以传入的第二个参数,即依赖数组。考虑以下示例:

import { useEffect } from 'react'
useEffect(() => {

}, []) // 
 array argument

在此处传递一个空数组将仅在组件挂载时运行效果函数,并在组件卸载时对其进行清理。这适用于您想要在组件挂载时跟踪或获取一些数据的情况。

下面是一个将值传递给依赖数组的示例:

import { useEffect } from 'react'
useEffect(() => {

}, [name]) // 
 array argument with a value

这里的含义是,当组件挂载时将调用效果函数,并且在name变量的值发生变化时再次调用。


来自 LogRocket 的更多精彩文章:

  • 不要错过来自 LogRocket 的精选时事通讯The Replay

  • 了解LogRocket 的 Galileo 如何消除噪音以主动解决应用程序中的问题

  • 使用 React 的 useEffect优化应用程序的性能

  • 在多个 Node 版本之间切换

  • 了解如何使用 AnimXYZ 为您的 React 应用程序制作动画

  • 探索 Tauri,一个用于构建二进制文件的新框架

  • 比较NestJS 与 Express.js


比较useEffect对象值

useEffectHook 接受一个可能执行一些副作用的函数参数:

useEffect(doSomething)

Hook 还接受第二useEffect个参数,即函数中的效果所依赖的值数组。例如:

useEffect(doSomething, [name])

在上面的代码中,该doSomething函数只会在name值更改时运行。此功能非常有用,因为您可能不希望在每次渲染后运行效果,这是默认行为。

然而,这带来了另一个问题。为了仅在发生更改时useEffect调用该doSomething函数name,它将先前的name值与其当前值进行比较,即prevName === name.

虽然这对原始 JavaScript 值类型非常有效,但如果name是对象呢?在 JavaScript 中,对象通过引用进行比较。从技术上讲,如果name是一个对象,它在每次渲染上总是不同的。因此,prevName === name支票将永远是假的。

暗示,该doSomething函数将在每次渲染后运行,这可能是一个性能问题,具体取决于您的应用程序类型。让我们回顾一下这个问题的解决方案。考虑下面的简单组件:

function RandomNumberGenerator () {
  const name = 'name'

  useEffect(
    () => {
      console.log('Effect has been run!')
    },
    [name]
  )

  const [randomNumber, setRandomNumber] = useState(0)

  return (
    <div>
      <h1>{randomNumber}</h1>
      <button
        onClick={() => {
          setRandomNumber(Math.random())
        }}
      >
        Generate random number!
      </button>
    </div>
  )
}

该组件呈现一个按钮和一个随机数。单击按钮后,将生成一个新的随机数:

请注意,useEffectHook 的效果取决于name变量:

useEffect(() => {
    console.log("Effect has been run!")
  }, [name])

在此示例中,name变量是一个简单的字符串。该效果将在组件挂载时运行。因此,console.log("Effect has been run!")将被调用。

在随后的渲染中,将进行浅比较,番茄畅听VIP解锁版,一款拥有海量音频的听书软件,资源全都无限制免费畅听!例如,prevName === name,其中表示新渲染之前prevName的先前值。name

字符串是按值比较的,所以"name" === "name"总是如此。因此,效果不会运行。因此,您只能获得一次日志输出Effect has been run!:

现在,将name变量更改为对象:

function RandomNumberGenerator() {
  // look here 
  const name = {firstName: "name"}

  useEffect(() => {
    console.log("Effect has been run!")
  }, [name])

  const [randomNumber, setRandomNumber] = useState(0);    

  return (
    <div>
      <h1>{randomNumber}</h1>
      <button onClick={()=> setRandomNumber(Math.random())}>Generate random number!</button>
    </div>
  );
}

在这种情况下,浅层检查在第一次渲染后再次执行。但是,由于对象是按引用而不是按值比较的,因此比较失败。例如,以下表达式返回false:

{firstName: "name"} === {firstName: "name"}

因此,效果会在每次渲染后运行,你会得到很多日志:

利用JSON.stringify

要阻止这种情况发生,请运行以下代码:

...useEffect(() => {
    console.log("Effect has been run!")
}, [JSON.stringify(name)])

通过使用JSON.stringify(name),被比较的值现在是一个字符串,并将按值进行比较。虽然这可行,但您应该谨慎行事。您应该只JSON.stringify在具有简单值和易于序列化数据类型的对象上使用。

使用手动条件检查

使用手动条件检查涉及跟踪先前的值,在这种情况下,name,并对其当前值进行深度比较检查。但是,您会注意到它涉及更多代码:

// the isEqual function can come from anywhere 
// - as long as you perform a deep check. 
// This example uses a utility function from Lodash
import {isEqual} from 'lodash'

function RandomNumberGenerator() {
  const name = {firstName: "name"}

  useEffect(() => {
    if(!isEqual(prevName.current, name)) {
      console.log("Effect has been run!")
    }
  })

  const prevName = useRef; 
  useEffect(() => {
    prevName.current = name
  })

  const [randomNumber, setRandomNumber] = useState(0);

  return <div>
    <h1> {randomNumber} </h1>
    <button onClick={() => { setRandomNumber(Math.random()) }}>
       Generate random number!
    </button>
  </div>
}

接下来,在运行效果之前,我们将检查值是否不相等:

!isEqual(prevName.current, name)

是什么prevName.current?使用 Hooks,您可以使用useRefHook 来跟踪值。在上面的示例中,下面的代码段负责:

const prevName = useRef; 
useEffect(() => {
    prevName.current = name
})

上面的命令会跟踪之前Hookname中使用的命令。useEffect我知道这可能会让人难以理解,因此我在下面包含了完整代码的注释良好的版本:

/**
 * To read the annotations correctly, read all turtle comments first 

 // - from top to bottom. 
 * Then come back to read all unicorns 
 - from top to bottom. 
 */


function RandomNumberGenerator() {
  // 
 1. The very first time this component is mounted, 
  // the value of the name variable is set below
  const name = {firstName: "name"}

  // 
 2. This hook is NOT run. useEffect only runs sometime after render
  // 
 6. After Render this hook is now run. 
  useEffect(() => {

  // 
 7. When the comparison happens, the hoisted value 
  // of prevName.current is "undefined". 
  // Hence, "isEqual(prevName.current, name)" returns "false" 
  // as {firstName: "name"} is NOT equal to undefined.
    if(!isEqual(prevName.current, name)) {

  // 
 8. "Effect has been run!" is logged to the console.       
  //console.log("Effect has been run!")
    }
  })

  // 
 3. The prevName constant is created to hold some ref. 
  const prevName = useRef; 

  // 
 4. This hook is NOT run 
  // 
 9. The order of your hooks matter! After the first useEffect is run, 
  // this will be invoked too.  
  useEffect(() => {
    // 
 10. Now "prevName.current" will be set to "name". 
    prevName.current = name; 
   // 
 11. In subsequent renders, the prevName.current will now hold the same 
    // object value - {firstName: "name"} which is alsways equal to the current 
    // value in the first useEffect hook. So, nothing is logged to the console. 
  // 
 12. The reason this effect holds the "previous" value is because 
    // it'll always be run later than the first hook.  
   })

  const [randomNumber, setRandomNumber] = useState(0)

  // 
 5. Render is RUN now - note that here, name is equal to the object, 
  // {firstName: "name"} while the ref prevName.current holds no value. 
  return
{randomNumber}
{ setRandomNumber(Math.random()) }}> Generate random number! }

使用useMemo钩子

在我看来,useMemoHook 提供了一个非常优雅的解决方案:

function RandomNumberGenerator() {
  // look here 

  const name = useMemo(() => ({
    firstName: "name"
  }), [])

  useEffect(() => {
      console.log("Effect has been run!")
  }, [name])

  const [randomNumber, setRandomNumber] = useState(0)
  return (
    <div>
      <h1>{randomNumber}</h1>
      <button onClick={()=> setRandomNumber(Math.random()) }> Generate random number! </button>
    </div>
  )
}

useEffectHook 仍然依赖于name值,但在这里,值name被记忆,由提供useMemo:

const name = useMemo(() => ({
    firstName: "name"
}), [])

useMemo接受一个返回特定值的函数。在这种情况下,对象{firstName: "name"}. to 的第二个参数useMemo是一个依赖数组,其工作方式与useEffect. 如果没有传递数组,则在每次渲染时重新计算该值。

传递一个空数组会计算安装组件时的值,而无需跨渲染重新计算值。name这通过跨渲染的引用保持值相同。

Hook现在useEffect应该可以按预期工作而无需多次调用效果,即使它name是一个对象。name现在是一个跨渲染具有相同引用的记忆对象:

...useEffect(() => {
      console.log("Effect has been run!")
}, [name]) // 
 name is memoized!

修复由于以下原因而中断的测试useEffect

在重构应用程序或组件以使用 Hooks 时,您可能面临的更令人不安的问题之一是,您的一些旧测试现在可能会无缘无故地失败。

如果您发现自己处于这个位置,请理解测试失败确实是有原因的,可悲的是。使用useEffect,需要注意的是效果回调不是同步运行的;它在渲染后的稍后时间运行。因此,与和useEffect并不完全相同。componentDidMountcomponentDidUpdatecomponentWillUnmount

由于这种异步行为,当您引入useEffect.

作为一种解决方案,在这种情况下,使用act()from 的实用程序ReactTestUtils会有很大帮助。如果您使用React 测试库进行测试,那么它在后台与act(). 使用 React 测试库,您仍然需要将测试中的状态更新或触发事件等手动更新包装到act():

act(() => {
    /* fire events that update state */
});
/* assert on the output */

我建议在 GitHub 上查看此讨论中的示例,以及有关在 中进行异步调用的act()讨论。最后,您将act()在此 GitHub 存储库中找到有关其工作原理的惊人示例。

如果你使用像Enzyme这样的测试库,并且在测试中有几个实现细节,比如调用instance()and之类的方法state(),你会遇到另一个与失败测试相关的问题。在这些情况下,您的测试可能会因将组件重构为功能组件而失败。

一种更安全的重构渲染道具 API 的方法

我倾向于到处使用渲染道具 API。值得庆幸的是,重构使用 render props API 的组件以使用基于 Hooks 的实现并不是什么大问题。但是,有一个小问题。考虑以下公开渲染道具 API 的组件:

class TrivialRenderProps extends Component {
  state = {
    loading: false,
    data: []
  }
  render() {
    return this.props.children(this.state)
  }
}

虽然这是一个人为的例子,但它已经足够好了!下面是我们将如何使用此组件的示例:

function ConsumeTrivialRenderProps() {
  return <TrivialRenderProps>
    {({loading, data}) => {
      return <pre>
        {`loading: ${loading}`} <br />
        {`data: [${data}]`}
      </pre>
    }}
  </TrivialRenderProps>
}

渲染ConsumeTrivialRenderProps组件只显示从渲染道具 API 接收到的loading和值:data

到目前为止,一切都很好!render props 的问题在于它会让你的代码看起来比你想要的更嵌套。值得庆幸的是,如前所述,将TrivialRenderProps组件重构为 Hooks 实现并不是什么大问题。

为此,您只需将组件实现包装在自定义 Hook 中并返回与以前相同的数据。正确完成后,重构的 Hooks API 将按如下方式使用:

function ConsumeTrivialRenderProps() {
  const { loading, setLoading, data } = useTrivialRenderProps()
  return <pre>
    {`loading: ${loading}`} <br />
    {`data: [${data}]`}
  </pre>
}

这看起来更整洁!下面是我们的自定义useTrivialRenderPropsHook:

function useTrivialRenderProps() {
  const [data, setData] = useState([])
  const [loading, setLoading] = useState(false)
 return {
    data,
    loading,
  }
}

就是这样!

// before 
class TrivialRenderProps extends Component {
  state = {
    loading: false,
    data: []
  }
  render() {
    return this.props.children(this.state)
  }
}

// after 
function useTrivialRenderProps() {
  const [data, setData] = useState([])
  const [loading, setLoading] = useState(false)

  return {
    data,
    loading,
  }
}

在处理大型代码库时,您可能会在许多不同的地方使用特定的渲染道具 API。更改组件的实现以使用 Hooks 意味着您必须更改组件在许多不同地方的使用方式。

我们可以在这里做一些权衡吗?绝对地!您可以重构组件以使用 Hook,但也可以公开渲染道具 API。通过这样做,您可以在整个代码库中逐步采用 Hook,而不必一次更改大量代码。下面是一个例子:

// hooks implementation 
function useTrivialRenderProps() {
  const [data, setData] = useState([])
  const [loading, setLoading] = useState(false)
  return {
    data,
    loading,
  }
}
// render props implementation 
const TrivialRenderProps = ({children, ...props}) => children(useTrivialRenderProps(props));
// export both 
export { useTrivialRenderProps };  
export default TrivialRenderProps;

通过导出这两种实现,您可以在整个代码库中逐步采用 Hook。以前的渲染道具消费者和新的 Hook 消费者都可以完美地工作:

// this will work 

function ConsumeTrivialRenderProps() {
  return <TrivialRenderProps>
    {({loading, data}) => {
      return <pre>
        {`loading: ${loading}`} <br />
        {`data: [${data}]`}
      </pre>
    }}
  </TrivialRenderProps>
}
// so will this 

function ConsumeTrivialRenderProps() {
  const { loading, setLoading, data } = useTrivialRenderProps()
  return <pre>
    {`loading: ${loading}`} <br />
    {`data: [${data}]`}
  </pre>
}

有趣的是,新的渲染道具实现也使用了 Hooks 下的 Hooks:

// render props implementation 
const TrivialRenderProps = ({children, ...props}) => children(useTrivialRenderProps(props));

处理状态初始化器

具有基于某些计算初始化某些状态属性的类组件并不少见。下面是一个基本示例:

class MyComponent extends Component {
  constructor(props) {
    super(props)
    this.state = { token: null }
    if (this.props.token) {
      this.state.token = this.props.token
    } else {
      token = window.localStorage.getItem('app-token');
      if (token) {
        this.state.token = token
      }
    }
  }
}

虽然我们的示例很简单,但它显示了一个通用问题。一旦您的组件安装,您可能会constructor根据一些计算在 中设置一些初始状态。

在这个例子中,我们检查是否token传入了一个 prop,或者本地存储中是否有一个app-token键,然后我们根据它设置状态。在重构为 Hooks 后,你如何处理这样的逻辑来设置初始状态?

也许 Hook 的一个鲜为人知的特性是你传递给HookuseState的参数, ,也可能是一个函数!initialStateuseStateuseState(initialState)

无论您从此函数返回什么,都将用作initialState. 下面的代码展示了组件被重构为使用 Hooks 后的样子:

function MyComponent(props) {
   const [token, setToken] = useState(() => {
     if(props.token) {
       return props.token 
     } else {
       tokenLocal = window.localStorage.getItem('app-token');
       if (tokenLocal) {
         return tokenLocal
       }
     }
   })   
}

从技术上讲,逻辑几乎保持不变。这里重要的是,useState如果您需要根据某些逻辑初始化状态,则可以使用函数 in。

结论

重构您的应用程序以使用 Hooks 并不是您必须要做的事情。您应该为自己和您的团队权衡不同的选择。您可以选择保留基于类的组件,因为它们仍然可以与基于功能的组件一起使用。但是,如果您选择重构组件以使用 Hooks API,那么我希望您在本文中找到了一些很棒的技巧。如果您有任何问题,请务必在下方留言,祝您编码愉快!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

pxr007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值