:::info
Hooks介绍:
Hooks是React 16.8版本新增的特性,它可以让我们在不编写class组件的情况下使用组件的状态state以及其他的react特性(比如生命周期),类组件能实现的功能我们都可以直接或者间接通过hooks来实现。
:::
1、为什么需要Hooks?
:::info
- class类组件的优点:组件可以定义、声明自己的state,用来维护和保存组件自身身的状态。
- 函数式的组件不可以,函数组件的每次调用(也就是组件的渲染)都会产生新的临时变量,在组件的内部产生,组件销毁对应的临时变量也会销毁。
- class类组件有自己的生命周期钩子,并在自己的生命周期中完成相应的逻辑任务,而函数式组件没有自己的生命周期方法。无法像类组件那样在某些时刻完成一些逻辑代码的执行。
- 比如在componentDidMount中发送网络请求,并且该生命周期函数只会执行一次。
- 函数式组件在学习hooks之前,如果在函数中发送网络请求,意味着每次重新渲染都会重新发送一次网络请求。
- class组件可以在状态改变时只会重新执行render函数以及我们希望重新调用的生命周期函数componentDidUpdate等。
- 函数式组件在重新渲染时,整个函数都会被执行,似乎没有什么地方可以只让它们调用一次。 :::
2、class类组件存在的问题?
:::info 在以往的开发中,类组件是存在一些列的问题,而总的来说,hook的出现,让我们可以彻底的不再使用类组件来编写相应的程序。全面转向使用函数组件,让我们以一种函数式编程思想来完成项目的迭代、开发。总的来说,hooks的出现,改变了编程习惯,hooks编程的颗粒度更加细化,方便和简洁。 :::
2.1 复杂组件变得难以理解
:::info
- 我们在最初编写一个class组件时,往往逻辑比较简单,并不会非常复杂。但是随着业务的增多,我们的class组件会变得越来越复杂、越来越臃肿。
- 比如componentDidMount中,可能就会包含大量的逻辑代码:包括网络请求、一些事件的监听(还需要在componentWillUnmount中移除)。
- 事实上、对于这样的class组件实际上非常难以拆分,因为它们的逻辑往往混在一起,强行拆分反而会造成过度设计,增加代码的复杂度。 :::
2.2 难以理解的class
:::info
- 学习ES6的class是学习react的一个障碍。
- 比如在class中,我们必须搞清楚this的指向到底是谁,而且书写起来还是一件比较麻烦的使用。所以需要花很多的精力去学习this和理解this的高级技巧。
- this的处理起来依然非常麻烦。 :::
2.3 组件里面的状态难以复用
:::info
- 在前面为了一些状态的复用我们需要通过高阶组件或render props
- 像我们之前学习的redux中connect或者react-router中的withRouter,这些高阶组件设计的目的就是为了状态的复用
- 或者类似于Provider、Consumer来共享一些状态,但是多次使用Consumer时,我们的代码就会存在很多嵌套。在开发与调试的过程中,代码变得臃肿、嵌套层次深,给调试程序与后期的维护带来了一定的困难。 :::
3、Hook组件的出现
:::info hook组件的出现,可以有效的解决上述出现的问题。hooks的优点:
- 它可以让我们在不编写class的情况下使用state以及其他的React特性。
- 但是我们可以由此延伸出非常多的用法,来让我们前面所提到的问题得到解决。
hooks的使用场景:
- Hook的出现基本可以代替我们之前所有使用class组件的地方(除了一些非常不常用的场景)。
- 但是如果是一个旧的项目,你并不需要直接将所有的代码重构为Hooks,因为它完全向下兼容,你可以渐进式的来使用它。
Hook只能在函数组件中使用,不能在类组件,或者函数组件之外的地方使用。 :::
4、useState函数Hook的使用
:::info useState原理:useState的本质上是一个函数,我们将一个普通的数据作为参数传入useState函数中,该函数给我们返回这个原始的状态数据和修改这个状态数据的方法,一旦我们的数据发生了改变,那么我们的页面将会自动进行渲染。 :::
4.1 useState的具体使用方法
// useState hook的使用方法
import { useState } from 'react';
export default function Counter() {
// 定义最初组件初始的状态 该hook的返回值是数组
// 数组的第一个元素是原始的数据,第二个参数是修改状态的方法
const [counter, setCounter] = useState(100)
// 定义操作数据的行为
function decrement() {
setCounter(counter - 10)
}
return (
<div>
// 在页面中可直接进行使用
<h2>{ counter }</h2>
// 修改数据
<button onClick={ () => setCounter(counter + 10) }>增加+10</button>
<button onClick={ () => decrement() }>减少-10</button>
</div>
)
}
4.2 useState使用的小细节
:::info
- useState可以多次使用,我们组件本身有多少个状态需要进行管理,那么我们就可以多次使用的useState的 hook。这样,可以让我们的状态颗粒度更细。
- 在修改状态的时候,我们必须传入一个新的状态,因为我们传入的新状态的值,在react的内部会进行比较,如果传入的是同一个值,那么react将不会重新渲染这个组件,所以在我们操作复杂类型的数据的时候,需要先将原始的数据进行拷贝,然后对拷贝的数据进行修改,最后将修改后的数据传给修改状态的函数。那么我们组件的状态就发生了变化,组件就会重新进行渲染。我们的页面就会展示最新的状态了。
- 记住,引用类型数据进行修改的时候,一定要先对其进行拷贝,后修改状态,这样我们的组件才会重新进行渲染。 ::: ```javascript import React, { useState } from ‘react’
export default function ComplexStateHook() { // 使用hook管理多个状态 我们的useState是可以使用多次的 const [counter, setCounter] = React.useState(100) const [age, setAge] = React.useState(18) const [todos, setTodos] = useState([‘吃饭’,’睡觉’, ‘打豆豆’])
// 复杂数据类型 const [friends, setFriends] = useState([ { id: ‘110’, name: ‘tom’, age: 13 }, { id: ‘111’, name: ‘jack’, age: 18 }, { id: ‘112’, name: ‘rose’, age: 20 } ])
// 添加要做的事情 正确的修改状态的方式 function addTodos() { const newTodos = […todos]; newTodos.push(“打代码”) setTodos(newTodos) }
// 添加要做的事情 错误的做法 function addTodo() { todos.push(“打游戏”) setTodos(todos) }
// 添加朋友 function addAge(index) { // 在这里需要对传入的数据进行拷贝 每次修改状态的时候应该传入一个新的状态 // 在react的内部会进行判断,如果传入的是同一个对象 那么页面将不会自动进行更新 // 如果传入的新状态不是同一个对象 页面将会自动更新 const newFriend = […friends]; newFriend[index].age += 1; // 修改状态 setFriends(newFriend) }
return (
当前数字: { counter }
年龄: { age }
要做的事情
-
{
todos.map((item, index) => {
return
- {item} }) }
好友列表
-
{
friends.map((item, index) => {
return
- 姓名:{ item.name } 年龄:{ item.age } }) }
<a name="nWJ5g"></a>
## 4.3 使用过程中需要的注意事项
> useState函数的参数可以传入一个箭头函数作为参数,箭头函数的返回值就是我们要管理的状态。
```javascript
// 可以使用箭头函数作为参数
const [counter, setCounter] = useState(() => 100)
conuter就是组件最新的状态
setCounter就是更新状态的方法 我们只需要将新的状态作为参数传递到这个更新状态的函数 react的内部就会主动调用我们组件render方法,更新界面
:::info
setCounter函数中可传入一个箭头函数 箭头函数的第一个参数是上一次修改的新的状态值。
我们也可以直接将最新的状态传入,组件也会进行更新
:::
// setCounter()修改状态 可传入一个箭头函数作为参数 箭头函数的参数 就是最新的状态的值
<button onClick={ () => setCounter( previous => previous + 1 ) }>修改状态</button>