React函数类组件及其Hooks学习

函数类组件

函数式组件和类式组件的区别:

React组件可以分为类组件和函数式组件,两者最明显的不同就是在语法上,函数组件是一个纯函数,它接收一个props对象返回一个react元素。而类组件需要去继承React.Component并且创建render函数返回react元素,这将会要更多的代码,虽然它们实现的效果相同。

  1. 函数式组件中没有state
  2. 函数式组件中没有生命周期(重点,涉及到生命周期的方法只能在类组件中定义。)

在较新的react版本看中添加了hooks,使得我们可以在函数组件中使用useState钩子去管理state,使用useEffect钩子去使用生命周期函数。从这个改版中我们可以看出作者更加看重函数组件,而且react团队曾提及到在react之后的版本将会对函数组件的性能方面进行提升。

为什么要使用函数式组件?

  1. Hooks是比高阶组件(HOC)和render props更优雅的逻辑复用方式。这个是很多人喊“真香”的原因。优雅的逻辑复用方式,会促进一个更加蓬勃的生态,这对于原本生态就很强的React来说是如虎添翼。会有更多的人愿意把自己的逻辑抽离成hooks(因为真的太优雅了),发布为library,为react生态添砖加瓦(看看这段时间各种基于hooks的状态管理工具)。我还认为,很快会出现一些“hooks生态圈”的“lodash”。
  2. 函数式组件的心智模型更加“声明式”。hooks(主要是useEffect)取代了生命周期的概念(减少API),让开发者的代码更加“声明化”:
  • 旧的思维:“我在这个生命周期要检查props.A和state.B(props和state),如果改变的话就触发xxx副作用”。这种思维在后续修改逻辑的时候很容易漏掉检查项,造成bug。
  • 新的思维:“我的组件有xxx这个副作用,这个副作用依赖的数据是props.A和state.B”。从过去的命令式转变成了声明式编程。
  • 其实仔细想一想,人们过去使用生命周期不就是为了判断执行副作用的时机吗?现在hooks直接给你一个声明副作用的API,使得生命周期变成了一个“底层概念”,无需开发者考虑。开发者工作在更高的抽象层次上了。
  • 类似的道理,除了声明副作用的API,react还提供了声明“密集计算”的API(useMemo),取代了过去“在生命周期做dirty检查,将计算结果缓存在state里”的做法。React内核帮你维护缓存,你只需要声明数据的计算逻辑以及数据的依赖。
  1. 函数式组件的心智模型更加“函数式”。react团队正在循序渐进地教育社区,为未来的并发模式打下基础。(其实react从一开始就受到了很多函数式编程的影响,现在推行函数式组件算是“回归初心”)。下面我会详细讨论函数式组件的心智模型。

Hooks概念及常用的Hooks

Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数。Hook 不能在 class 组件中使用 —— 这使得你不使用 class 也能使用 React。

React官网是这样描述Hooks的:Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据),而并非强制按照生命周期划分。你还可以使用 reducer 来管理组件的内部状态,使其更加可预测。

1. useState: State的Hook

State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作。

语法

const [xxx, setXxx] = React.useState(initValue)

useState()说明:

参数: 第一次初始化指定的值在内部作缓存;

返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数。

可以多次使用useState()

setXxx()2种写法:

setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值

setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值

示例

import React, { useState } from 'react';

function Example() {
  // 声明一个叫 “count” 的 state 变量。
  const [count, setCount] = useState(0);
    
  // 加的回调 setXxx()第二种写法
  function add() {
      setCount(count => count + 1);
  }

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

export default Example

2. useEffect: 副作用的Hook

Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)。

我们之前可能已经在React组件中执行过数据获取、订阅或者手动修改过DOM。我们统一把这些操作称为“副作用”,或者简称为“作用”。它跟 class 组件中的 componentDidMountcomponentDidUpdatecomponentWillUnmount 具有相同的用途,只不过被合并成了一个 API。

React中的副作用操作

  • 发ajax请求数据获取
  • 设置订阅 / 启动定时器
  • 手动更改真实DOM

语法和说明:

  • 通过使用这个 Hook,你可以告诉 React 组件需要在渲染后执行某些操作
  • useEffect 放在组件内部让我们可以在 effect 中直接访问 count state 变量(或其他 props)。
  • 默认情况下,它在第一次渲染之后和每次更新之后都会执行,即不加第二个参数的情况下。

总体说明:

useEffect(() => { 
	// 在此可以执行任何带副作用操作
	return () => { // 在组件卸载前执行
	// 在此做一些收尾工作, 比如清除定时器/取消订阅等
	}
}, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行

可以多次使用useEffect

替代componentDidMount:

useEffect(() => { 
	// 在此可以执行任何带副作用操作,来初始化代码
}, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行

替代componentDidUpdate:

useEffect(() => { 
	// 在此可以执行任何带副作用操作
}, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行,此时不能为空,数组内的值为所监控的state的值,如果不加此参数,默认为所有的state的值
// 例如如下代码
useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新

替代componentWillUnmount:

useEffect(() => { 
	return () => { // 在组件卸载前执行
	// 在此做一些收尾工作, 比如清除定时器/取消订阅等
	}
})

实例:

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

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

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

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

3. useRef: ref的Hook

Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据,跟React.createRef()类似。

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。

语法:

const refContainer = useRef()

ref 对象的 .current 属性为相应的 DOM 节点。

实例:

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` 指向已挂载到 DOM 上的文本输入元素
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

函数类组件中使用props

使用方式类似类组件的props使用,父级组件需要暴露接口给子组件,子组件才能接收,子组件接收数据的方法:直接作为函数的参数(props)传给子组件。

import React, { useState } from 'react'
import Title from "../Title"
//父级组件传过去
function Parent() {
    let [count, setCount] = useState(1)
    const add = (count) => { setCount(count) }
    return (<div>
        //类似类组件中的接口传递数据到子组件
        <Title count={count} />   
        <button onClick={() => add(count + 1)}>+</button>
    </div>)
}

export default Parent;

//子组件接收
import React from 'react'

function Title(props) {
    return (
        <h1>{props.count}</h1>
    )
}

export default Title;
posted @ 2022-02-28 21:49  bleaka  阅读(672)  评论(2编辑  收藏  举报