React-Hooks 初识 (一): 认识Hooks以及useState的简单应用

1,423 阅读6分钟

前言:最近公司进行了技术分享,刚好我的选题是react-hooks,近期就给大家简单介绍下react-hooks的常用函数以及需要注意的事项(尤其是react-hooks里的capture value 特性,写业务经常遇到的坑!!!) 欢迎大家相互交流指正。话不多说,开搞开搞!!

一、Hook简介以及引入的目的

React Hooks就是用函数的形式代替原来的继承类的形式,并且使用预函数的形式管理state,有Hooks可以不再使用类的形式定义组件了。也就是说原来把组件分为有状态组件和无状态组件,有状态组件用类的形式声明,无状态组件用函数的形式声明。那现在所有的组件都可以用函数来声明了。

使用 React Hooks 相比于从前的类组件有以下几点好处:

1  代码可读性更强,原本同一块功能的代码逻辑被拆分在了不同的生命周期函数中,容易使开发者不利于维护和迭代,通过 React Hooks 可以将功能代码聚合,方便阅读维护

2  组件树层级变浅,在原本的代码中,我们经常使用 HOC/render props 等方式来复用组件的状态,增强功能等,无疑增加了组件树层数及渲染,而在 React Hooks 中,这些功能都可以通过强大的自定义Hooks 来实现,就是可以利用自定义Hooks来进行组件树层的渲染。

注意:

  • 只能在 函数内部的最外层 调用 Hook,不要在循环、条件判断或者子函数中调用
  • 只能在 React 的函数组件中调用 Hook,不要在其他 JavaScript 函数中调用

二、React Hooks 编写形式对比

需求:点我们点击按钮时,点击数量不断增加。

Class组件的写法:在类组件中,我们使用 this.state 来保存组件状态,并对其修改触发组件重新渲染。

import React, { Component } from 'react';

class Example extends Component {
    constructor(props) {
        super(props);
        this.state = { count:0 }
    }
    render() { 
        return (
            <div>
                <p>You clicked {this.state.count} times</p>
                <button onClick={this.addCount.bind(this)}>Chlick me</button>
            </div>
        );
    }
    addCount(){
        this.setState({count:this.state.count+1})
    }
}

export default Example;

React Hooks 写法:

import React, { useState } from 'react';
function Example(){
    const [ count , setCount ] = useState(0);  // 这里就是用hook函数useState进行数据修改
    return (
        <div>
            <p>You clicked {count} times</p>
            <button onClick={()=>{setCount(count+1)}}>click me</button>
        </div>
    )
}
export default Example;

从这两个程序的对比上可以看出Hooks本质上就是一类特殊的函数,注意!!! React Hooks不能在if...else...这样的条件语句中进行调用,必须要按照相同的顺序进行渲染。总之:就是React Hooks不能出现在条件判断语句中,因为它必须有完全一样的渲染顺序

三、useState 保存组件状态

useState是react自带的一个hook函数,它的作用是用来声明状态变量。

那我们从三个方面来看useState的用法,分别是声明、读取、更新(修改)。这三个方面掌握了,你基本也就会使用useState了。

3.1.1、 声明:

代码如下:

这种方法是ES6语法中的数组解构
const [ count , setCount ] = useState(0); //这是初始值0传入
如果不写成数组解构,上边的语法要写成下面的三行:
let _useState = userState(0) 
let count = _useState[0]
let setCount = _useState[1]
 # useState这个函数接收的参数是状态的初始值(Initial state),它返回一个[数组],这个数组的第0位是当前的状态值,第1位是可以改变状态值的方法函数。

useState接收的参数是状态的初始值,返回的是一个数组, 数组第0位是当前状态值,第1位是可以改变状态值的方法函数 .所以上面的代码的意思就是声明了一个状态变量为count,并把它的初始值设为0,同时提供了一个可以改变count的状态值的方法函数。

3.1.2、读取

<p>You clicked {count} times</p>

我们读取是很简单的。只要使用{count}就可以,因为这时候的count就是JS里的一个变量,想在JSX中使用,值用加上{}就可以。

3.1.3、更新

更新分为以下两种方式,即直接更新和函数式更新,其应用场景的区分点在于:

直接更新不依赖于旧 state 的值;函数式更新依赖于旧 state 的值;

引入问题:为什么函数组件每次渲染都会重新进行调用把函数体重新走一次, 但是useState的值却没有一直被赋予初值,

// 直接更新
setState(newCount);   /  不依赖旧的 state 值

// 函数式更新
setState(prevCount => prevCount + 1);    / 依赖旧的 state 值
   

<button onClick={()=>{setCount(count+1)}}>click me</button>  / 直接更新

<button onClick={()=>{setCount((count)=>count+1)}}>click me</button>  / 函数式更新

直接调用setCount函数,这个函数接收的参数是修改过的新状态值。接下来的事情就交给React, 如果状态值发生改变,他会重新渲染组件。并且注意,state值不可以直接修改,只能通过setState来进行修改!!

案例:对直接更新和函数式更新区别的例子:

3.2、实现合并

注意: useState 直接替换 它的要 被更新对象 我们可以用函数式的 setState 结合展开运算符( ... )来达到合并更新对象的效果。

const [value,setValue]=useState({
	name:'',
  age:''
})
=====================

setState(value => {
  // 也可以使用 Object.assign
  return {...value, ...updatedValues};
});

如果要修改原数据其中某一个属性值
setState({...value, name:'张三'})

3.3、惰性初始化 state,处理复杂的逻辑

initialState 初始参数只会在组件的初始渲染中起作用,后续渲染时会被忽略。其应用场景在于:创建初始 state 很昂贵时,例如需要通过复杂计算获得;那么则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用,注意一点,这个函数只能进行同步操作!!!!

const [state, setState] = useState(() => {
  const initialState = someExpensiveComputation(props);
  return initialState;
});

=========实际案例===========
 const sum = (a, b) => {
        return a + b
    }
    const [initData, setInitData] = useState(() => {
        const a = sum(1, 2)
        return a;
    })
    return (
        <>{initData} </>
    )

重要: 对比箭头函数写法与直接调用函数:

3.4、小综合案例:

case:  useState 初始值传入的是一个对象,然后对这个对象进行修改值

import React, { useState } from "react";
function App() {
  const [obj, setObject] = useState({
    count: 0,
    name: "alife"
  });
  return (
    <div className="App">
      Count: {obj.count}
      <button onClick={() => setObject({ ...obj, count: obj.count + 1 })}>+</button>
      <button onClick={() => setObject({ ...obj, count: obj.count - 1 })}>-</button>
    </div>
  );
}

3.5、一些重点

  • 使用多个 state 变量,用相应的set函数进行修改state状态;

  • 注意,State Hook 只有更新了state 值才会引起组件渲染

  • 如果使用函数改变数据, 若数据和之前的数据完全相等(使用Object.属性名 eg-> obj:{name:'张三'} obj.name), 则不会重新渲染, 但如果是引用类型的,仍会进行赋值

  • setState在原生事件和setTimeout、setInterval里是同步进行的(原因在于这些事件帮助state脱离了React的掌控)

讨论题: 为什么useState不能写在if判断里?