React教程(五):Hooks

传送门:
React教程(一):React基础
React教程(二):React组件基础
React教程(三):React组件通信
React教程(四):React组件进阶

Hooks概念

1.什么是hooks

hooks本质:一套能够使函数组件更强大更灵活的钩子

React 体系里组件分为类组件和函数组件
经过多年的实战,函数组件是一个更加匹配React设计理念UI=f(data),也更有利于逻辑拆分与重用的组件表达形式,而先前的函数组件是不可以有自己的状态的,为了能让函数组件可以拥有自己的状态,所以React从v16.8开始,Hooks应允而生。

注意点:

  1. 有了hooks之后,为了兼容老版本,class类组件并没有被移除,两者都可以使用。
  2. 有了hooks之后,不能把函数组件当成无状态组件了,因为hooks为函数组件提供了状态。
  3. hooks只能在函数组件中使用。

2. Hooks解决了什么问题

Hooks的出现解决了俩个问题
1.组件的状态逻辑复用 2.class组件自身的问题

  1. 组件的逻辑复用
    在hooks出现之前,react先后尝试了mixins混入,HOC高阶组件,render-props等模式
    但是都有各自的问题,比如mixin的数据来源不清晰,高阶组件的嵌套问题等等。
  2. class组件自身问题
    class组件像一个厚重的航母,又大又重,它提供了很多东西,有很多学习成本,比如各种生命周期函数,this指向问题等等,而我们更多时候需要的是灵活小巧小战舰。

3. Hook优势总结

  1. 告别难以理解的class
  2. 解决业务逻辑难以拆分的问题
  3. 使状态逻辑复用变得简单可行
  4. 函数组件在设计思想上,更加契合React的理念。

常用的Hooks介绍与使用

useState

1.基础使用

作用: useState为函数组件提供状态(state)
使用步骤:

  1. 导入useState函数
  2. 调用useState函数,并传入状态的初始值
  3. useState函数的返回值中,拿到状态和修改状态的方法
  4. 在JSX中展示状态
  5. 调用修改状态的方法更新状态

代码实现:

// 引入react核心包,导入useState
import React, { useState } from "react";

function App() {
  // 调用useState函数并传入state初始值,从该函数返回值中拿到状态money和修改状态的方法setMoney
  const [money, setMoney] = useState(1000);
  const addMoney = () => {
    setMoney(money + 1000);
  };
  return (
    <div>
      icy的钱:{money}
      <button onClick={addMoney}>点击让icy的钱变多</button>
    </div>
  );
}

export default App;

效果:
在这里插入图片描述

2.状态的读取和修改

读取状态:
该方法提供的状态,是函数内部的局部变量,可以在函数的任何位置使用。
修改状态:

  1. setMoney是一个函数,参数表示最新的状态值
  2. 调用该哈数后,将使用新值替换旧值。
  3. 修改状态后,由于状态发生变化,会引起视图变化。
    注意事项:
    修改状态的时候,一定要使用新的状态替换旧的状态,不能直接修改旧的状态,尤其是引用类型。

3.组件的更新过程

// 当调用setMoney的时候,组件内部代码会被执行一次!

// 首次渲染
// 首次被渲染的时候,组件内部的代码会被执行一次
// 其中useState也会执行一次

// 更新渲染 money会更新
// 1.app组件会再次渲染,这个函数会再次执行
// 2.useState再次执行,得新的money值不是初始值1000而是setMoney后的新值

function App() {
  const [money, setMoney] = useState(1000);
  const addMoney = () => {
    setMoney(money + 1000);
  };
  return (
    <div>
      icy的钱:{money}
      <button onClick={addMoney}>点击让icy的钱变多</button>
    </div>
  );
}

4.使用注意点

  1. useState函数可以执行多次,每次执行互相独立,每调用一次为函数组件提供一个状态
    设置初始值,可以以任意类型的值为初始值
const [str,setStr] = useState('icy') // 以字符为初始值
const [list,setList] = useState([]) // 以数组为初始值
  1. 只能出现在函数组件中,且在函数组件的第一层,不能嵌套在if/for其他函数中,一般情况放函数最顶端。
  2. 可以通过谷歌开发者工具react-devtools查看hooks状态。

useEffect

1.什么是副作用

  副作用是相对于主作用来说的,一个函数除了主作用就是副作用,对于React组件来说,主作用是根据(state/props)渲染UI,除此之外都是副作用(如手动修改dom)。
常见的副作用:

  1. 数据请求
  2. 手动修改dom
  3. localstorage操作

useEffect函数的作用就是为reac函数组件提供副作用处理的。

2.基础使用

// 引入react核心包,1.导入useState 、useEffect
import React, { useState, useEffect } from "react";

function App() {
  const [money, setMoney] = useState(1000);
  const addMoney = () => {
    setMoney(money + 1000);
  };
  // 2.定义副作用
  useEffect(() => {
    // 3.dom操作
    document.title = `icy的${money}`;
  });
  return (
    <div>
      icy的钱:{money}
      <button onClick={addMoney}>点击让icy的钱变多</button>
    </div>
  );
}

注意:该函数每次调用都会执行一次,当我们通过修改状态(setState)更新组件时,useEffect也会不断执行。

3. 通过依赖项控制执行时机

// 1.默认状态(无依赖)
  // 组件初始化的时候执行一次,等到每次更新后会再次执行
  useEffect(() => {
    document.title = `icy的${money}`;
  });

  // 2.添加依赖项 空数组[]
  // 只在首次渲染时执行一次,之后不再执行
  useEffect(() => {
    console.log("icy");
  }, []);

  // 3.添加特定依赖项
  // 副作用函数在首次渲染时执行,在依赖项发生变化时才会再次执行。
  useEffect(() => {
    console.log("icy has much money");
  }, [money]);

自定义Hooks

useWindowScroll

需求描述: 自定义一个hooks函数,实现获取滚动距离Y
如: const [y] = useWindowScroll()

import { useState } from "react";

export function useWindowScroll() {

  const [y, setY] = useState(0);
  window.onscroll = () => {
    // 获取页面滚动距离
    const top = document.documentElement.scrollTop || document.body.scrollTop;
    setY(top);
  };

  return [y];
}

useStorage

需求描述: 自定义一个hooks函数,当值发生变化可以自动同步到本地localStorage

import { useEffect, useState } from "react";

export function useStorage(key, defaultValue) {
  const [value, setValue] = useState(defaultValue);
  useEffect(() => {
    localStorage.setItem(key, value);
  }, [value]);
  return [value, setValue];
}

还有很多自定义Hooks的非常好用的api可以使用
有一个叫ahooks的库:https://ahooks.js.org/
感兴趣的可以自行了解。

Hooks进阶

1.useState - 回调函数的参数

使用场景
参数只会在组件的初始值渲染中起作用,后续渲染时会被忽略。如果初始state需要通过计算才能获得,则可以传入一个函数,在函数计算并返回初始的state,此函数只在初始渲染时被调用。
语法

const [name,setName] = useState(()=>{
	// 具体的计算逻辑
	return xxxx
})
  1. return 的值作为name的初始值
  2. 该函数只在初始化时执行一次
  3. 如果只是初始化一个普通的数据,那么直接useState(‘普通数据’)即可,如果初始化一个只能通过计算获取的数据,则可以使用useState(带返回值的函数)这种方法。

2.UseFffect - 清理副作用

使用场景:
在组件被销毁时,如果有些副作用操作需要被清理,就可以使用此语法,比如常见的定时器。

场景1:
组件销毁后,定时器还在执行,这时候需要return一个函数用来清除定时器。
代码演示:


const Test = () => {
  useEffect(() => {
    let timer1 = setInterval(() => {
      console.log("定时器执行");
    }, 1000);
    return () => {
      console.log("清理副作用函数执行了");
      // 编写清理副作用的代码
      clearInterval(timer1);
    };
  }, []);
  return <div>test</div>;
};
function App() {
  const [isShow, setIsShow] = useState(true);
  return (
    <div className="wrapper">
      {isShow ? <Test /> : null}
      <button
        onClick={() => {
          setIsShow(!isShow);
        }}
      >
        切换
      </button>
    </div>
  );
}

3. useEffect 发送网络请求

使用场景
如何在useEffect中发送网络请求,并且封装同步async await操作。
语法要求:
不可以直接在useEffect的回调函数外层直接包裹await,因为异步会导致清理函数无法直接返回
错误写法:

  useEffect(async () => {
      const res = await fetch("http://geek.itheima.net/v1_0/channels")
        .then((response) => response.json())
        .then((data) => console.log(data));

      console.log(res);
    }
  }, []);

正确写法
在内部单独定义一个函数,然后把这个函数使用async包装成同步


  useEffect(() => {
    async function fetchData() {
      const res = await fetch("http://geek.itheima.net/v1_0/channels")
        .then((response) => response.json())
        .then((data) => console.log(data));

      console.log(res);
    }
    fetchData();
  }, []);

4. useRef

以前写过一篇博客,可以看看:快速理解useRef
使用场景:
1.在函数组件中获取真实dom
2.保存值

使用步骤:
1.导入useRef
2.执行useRef函数并传入null,返回值为一个对象,内部有一个current属性存放拿到的dom对象(组件实例)
3,通过ref绑定要获取的元素或组件

获取dom

// 引入react核心包,1.导入useState 、useEffect,useRef
import React, { useEffect, useRef } from "react";

function App() {
  const h1Ref = useRef(null);
  useEffect(() => {
   // 组件渲染后才能拿到h1Ref,而副作用在组件渲染完成后才执行
    console.log(h1Ref.current);
  }, []);
  return (
    <div className="wrapper">
      <h1 ref={h1Ref}>11111111111</h1>
    </div>
  );
}

获取组件实例:

函数组件由于没有实例,不能使用ref获取,如果想获取组件实现,必须是类组件。


class Test extends React.Component {
  getName = () => {
    return "my name is icy";
  };
  state = {
    name: "icy",
  };
  render() {
    return <div>111</div>;
  }
}

function App() {
  const testRef = useRef(null);
  useEffect(() => {
    console.log(testRef.current);
  }, []);
  return (
    <div className="wrapper">
      <Test ref={testRef} />
    </div>
  );
}
}

current 里拿到的是组件的实例对象,所以可以拿到组件内的方法,直接使用testRef. xxx点语法可以调用。
React教程(五):Hooks_第1张图片

5.useContext

实现跨组件通信
使用步骤:

  1. 使用createContext创建context对象
  2. 在顶层组件通过Provider提供数据
  3. 在底层组件通过useContext函数获取数据

代码实现:

// 引入react核心包,1.导入useState 、useEffect,useRef
import React, { createContext, useContext, useEffect, useRef } from "react";

// 创建context对象
const Context = createContext();

function TestA() {
  return <TestB />;
}
function TestB() {
  return <TestC />;
}
function TestC() {
  const name = useContext(Context);
  return <div>my name is {name}</div>;
}
function App() {
  const testRef = useRef(null);
  useEffect(() => {
    console.log(testRef.current);
  }, []);
  return (
    // Provier包裹顶层组件
    <Context.Provider value={"icy"}>
      <div className="wrapper">
        <TestA />
      </div>
    </Context.Provider>
  );
}

export default App;

你可能感兴趣的:(react,web前端,react.js,javascript,前端)