React基础知识四 Hooks

什么是hooks? (coderwhy)

hooks是react 16.8(2019年)出的新特性。

react有两种形式来创建组件------类式和函数式 。在hooks之前类式组件就是react最主流的编程方式。 这个时候,函数式组件是非常鸡肋的,几乎没什么用。因为函数式组件不能保存数据状态,所以只能用于一些简单的展示的场景,传什么数据就展示什么数据(因为只有props是可以用的)。并且函数组件是没有生命周期的。

但因为函数式编程的思想在前端是普遍流行和推崇的。我猜想react团队在设计函数式组件的时候肯定已经想到了这个问题。但可能当时没有想到合适方式实现函数式组件的完整功能。

对于开发者来说,使用类式组件或者函数式组件来开发功能实际上都是无所谓,谁好用就用谁。但设计者为了实现函数式组件可以说是绞尽脑汁。至于设计出来的东西好不好用另说。但函数式组件的这条路是一定要走下去的。

还有一个促使hooks诞生的原因是类式组件存在一些缺点。例如类式不好对功能进行拆分。当然hooks本身是否存在别的缺点我们另说。class概念难以理解以及this的指向问题处理对于初学者来说都是比较麻烦的。

1.hooks是完全可选的,你不用hooks,用类式组件也是完全没有问题的。
2.hooks是100%向后兼容的。hooks不包含任何破坏性改动。

3.hooks的代码比类组件相对少一些。

useState和useEffect的说明

你只要把这两个hooks学了,已经可以应对90%的情况了,因为这两个hooks可以处理state和生命周期问题。

useState的使用和使用规则

useState是一个hook,但他和react的state本质是没什么关系的,只是功能上是一样的------有一个地方能存储组件的数据。

基本使用

我们定义一个useState。数组的第一个参数是变量的名字,第二个名字是用于修改变量的函数,可以传一个初始值。

这是hooks的基本使用规则,都是这样使用的。

要修改值,我们直接调用自己定义的setCount,把原来的count和num相加,就实现了计算器的效果。

bash 复制代码
import { useState } from "react";

function App() {
  const [count, setCount] = useState(0);
  function handleAdd(num) {
    setCount(count + num);
  }
  return (
    <div className="App">
      <div>
        {count} <button onClick={(e) => handleAdd(1)}>+1</button>
      </div>
    </div>
  );
}

export default App;

我们可以只写一个变量名,不写set方法,其实数组的两个变量,就是变量的get和set方法。

bash 复制代码
import { useState } from "react";
function App() {
  const [user] = useState({ name: "Tom", age: 18 });
  //const [user,setName] = useState({ name: "Tom", age: 18 });
  return (
    <div className="App">
      <div>{user.name + " " + user.age}</div>
    </div>
  );
}

export default App;

使用规则

不是所有的地方都可以使用hooks,hooks必须定义在函数顶层,不能定义在if,for里面。官方就是这样规定的。

hooks简单原理

在函数组件执行完成后,count变量已经被销毁了,但是count的值被react保留了起来,当页面再次渲染的时候,会重新创建一个count变量,并把原来的值赋值给新创建的这个count变量。

bash 复制代码
const [count, setCount] = useState(0);

useEffect的使用

什么是useEffect

effect的意思是影响,效果,效应。其实说简单点,这个hooks就是用来监听useState的数据变化的。同时可以实现类似生命周期的效果,但实际上,生命周期效果只是useEffect的一个特性,最直观的作用还是监听数据的变化。

useEffect基本使用

下面的代码实现一个计算器。

bash 复制代码
import { useEffect, useState } from "react";

function App(props) {
  const [count, setCount] = useState(100);
  useEffect(() => {
    console.log(count);
  });

  function handleAdd(num) {
    setCount(count + num);
  }

  return (
    <div>
      App:{count}
      <button onClick={(e) => handleAdd(1)}>+1</button>
    </div>
  );
}

export default App;

第一次页面渲染的时候,useEffect里面的log输出了。

我们点击按钮做累加。useEffect回调函数被多次执行了。但这和count这个setState是没有关系的。也就是说,只要页面重新渲染了,useEffect就会重新执行。

啥?那useEffect有什么用?别着急,下一小节你就明白了。

多个useEffect的使用和绑定setState变量

上面的例子可能挺让人困惑的,到底是要干什么?下面就来好好说说useEffect的本质是什么。看完这个小节,就能很好理解useEffect是怎么用的了。

我们定义两个useState变量,count和user。并且定义两个useEffect方便绑定这两个useState。

useEffect接收两个参数,第一个参数就是我们前面用的回调函数,第二个参数是一个数组,里面放useState产生的变量,放什么变量,回调函数就只会在放的变量发生改变的时候回调。

bash 复制代码
import { useEffect, useState } from "react";

function App(props) {
  const [count, setCount] = useState(100);
  const [user, setUser] = useState({ name: "Tom", age: 18 });
  useEffect(() => {
    console.log("监听count:" + count);
  }, [count]);

  useEffect(() => {
    console.log("监听user:", user);
  }, [user]);

  function handleAdd(num) {
    setCount(count + num);
  }

  return (
    <div>
      App:{count}
      <button onClick={(e) => handleAdd(1)}>+1</button>
    </div>
  );
}

export default App;

页面加载的时候,我们可以看到两个useEffect输出下面的内容。

当点击+1的时候,count的useEffect回调函数执行了,而user的useEffect回调函数没有执行。这说明,在指定了第二个参数后,useEffect的回调函数只会在第二个参数的值发生变化的时候才会回调。

假设写成下面这个样子,那么在user或者count发生改变的时候,回调函数会被执行。

js 复制代码
  useEffect(() => {
  
  }, [user,count]);

假如写成下面这个样子,那么user,count发生变化,这个函数都不会执行。也就是任何useState发生变化,这个函数都不会回调。但是在第一次渲染的时候函数还是会回调的。

bash 复制代码
  useEffect(() => {
  
  }, []);

假如写成下面这个样子,也就是最开始的代码,又是怎么回事呢?

写成这样表示,任何useState发生变化,这个函数都会回调。

bash 复制代码
  useEffect(() => {
  
  });

有什么不明白的请参考小结。

useEffect的返回值

返回值是一个回调函数,这个函数在组件被销毁或者监听的数据更新的时候回调。

bash 复制代码
  useEffect(() => {
  	return ()=>{
  	}
  });

实用技巧一 充当componentDidMount和componentWillUnmount

首先,我们第二个参数写空数组,这样任何useState的更新都不会触发这个useEffect。但是在组件第一次渲染和销毁的时候,会分别执行下面两个位置的代码,也就正好和componentDidMount和componentWillUnmount这两个生命周期的执行时机是一样的。

这是useEffect的使用技巧,react作者是否有意这样设计我也不是很清楚。

bash 复制代码
  useEffect(() => {
    //这里相当于componentDidMount
  	return ()=>{
  	//这里相当于componentWillUnmount
  	}
  }, []);

实用技巧二 充当componentDidUpdate

因为没有填第二个参数,所以在有任何数据更新的时候都会触发这个方法,相当于是componentDidUpdate生命周期

bash 复制代码
  useEffect(() => {
     //有数据更新的时候触发
  });

小结

1.useEffect主要目的是监听useState数据的变化。

2.useEffect充当生命周期只是他的附加功能。

useContext的使用(掌握)

定义两个Context。

bash 复制代码
import { createContext } from "react";

const ThemeContext = createContext();
const UserContext = createContext();

export { ThemeContext, UserContext };

使用context并给初始值。

bash 复制代码
root.render(
  // <React.StrictMode>
  <UserContext.Provider value={{ name: "Tom", age: 18 }}>
    <ThemeContext.Provider value={{ color: "red", fontSize: 18 }}>
      <App />
    </ThemeContext.Provider>
  </UserContext.Provider>

  // </React.StrictMode>
);

通过useContext直接获取到context的值就可以直接使用了。

如果不用useContext,你就要使用xxxContext.Consumer来使用,多个context的时候还要嵌套。这里就体现了hooks的一个优点。

bash 复制代码
import { useContext } from "react";
import { ThemeContext, UserContext } from "./context";

function App(props) {
  const user = useContext(UserContext);
  const theme = useContext(ThemeContext);
  return (
    <div>
      App:
      <div>user:{user.name + " " + user.age}</div>
      <div style={{ color: theme.color, fontSize: theme.fontSize }}>theme</div>
    </div>
  );
}
export default App;

useReducer的使用(了解)

这个东西在数据比较复杂的时候,有点用处,但本身用法也复杂。不推荐使用。有用到再说。

useCallback和useMemo的使用

这两个hooks是用来做性能优化的。

useCallback的使用

在将一个函数传递给子组件的时候,用useCallback做性能优化。

useRef的使用

和类组件的createRef是非常类似的,只是我们用useRef。

在函数组件里面,ref返回的值始终是同一个对象。

bash 复制代码
import { memo, useRef } from "react";

const App = memo((props) => {
  const refBtn = useRef();
  const refInput = useRef();

  function handleClick() {
    //获取到组件自身
    console.log(refBtn.current);
    //获取input焦点
    refInput.current.focus();
  }

  return (
    <div>
      App
      <button ref={refBtn} onClick={handleClick}>
        点击
      </button>
      <input ref={refInput}></input>
    </div>
  );
});
export default App;

自定义hooks

自定义hook监听生命周期

其实就是把useEffect封装了一下,这样就可以监听组件的创建和销毁了。

bash 复制代码
function useLogLife(name = "") {
  useEffect(() => {
    console.log(name, "组件被创建了");
    return () => {
      console.log(name, "组件被销毁了");
    };
  }, []);
}

这个例子可以在show变化的时候创建和销毁About和Home组件。

bash 复制代码
import { memo, useEffect, useState } from "react";

function useLogLife(name = "") {
  useEffect(() => {
    console.log(name, "组件被创建了");
    return () => {
      console.log(name, "组件被销毁了");
    };
  }, []);
}

const About = memo((props) => {
  useLogLife("about");
  return <div>About</div>;
});

const Home = memo((props) => {
  useLogLife("home");
  return <div>Home</div>;
});

const App = memo((props) => {
  useLogLife("app");
  const [show, setShow] = useState(false);
  return (
    <div>
      App Component
      {show ? <Home /> : ""}
      {!show ? <About /> : ""}
      <button onClick={(e) => setShow(!show)}>点击</button>
    </div>
  );
});
export default App;

获取context

首先我们定义context,和原来没什么区别。

bash 复制代码
import { createContext } from "react";

const UserContext = createContext();
const TokenContext = createContext();

export { UserContext, TokenContext };

提供初始数据

bash 复制代码
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./自定义hook获取context/App";
import { TokenContext, UserContext } from "./自定义hook获取context/context";

const root = ReactDOM.createRoot(document.getElementById("root"));

root.render(
  // <React.StrictMode>
  <UserContext.Provider value={{ name: "Tom", age: 18 }}>
    <TokenContext.Provider value={"this_is_token"}>
      <App />
    </TokenContext.Provider>
  </UserContext.Provider>

  // </React.StrictMode>
);

我们自定义一个useUserToken,把UserContext和TokenContext通过useContext套一层,这样我们就不需要使用consumer了。

bash 复制代码
import { useContext } from "react";
import { TokenContext, UserContext } from ".";
function useUserToken() {
  const user = useContext(UserContext);
  const token = useContext(TokenContext);
  return [user, token];
}
export default useUserToken;

最后,我们只需要使用我们自定义的useUserToken就可以同时获取到user和token的数据了。

bash 复制代码
import { memo } from "react";
import useUserToken from "./context/useUserToken";

const Home = memo((props) => {
  const [user, token] = useUserToken();
  return <div>Home: {user.name + " " + token}</div>;
});

const About = memo((props) => {
  const [user, token] = useUserToken();
  return <div>About: {user.name + " " + token}</div>;
});

const App = memo((props) => {
  const [user, token] = useUserToken();
  return (
    <div>
      App: {user.name + " " + token}
      <Home />
      <About />
    </div>
  );
});
export default App;

监听窗口滚动位置

定义一个下面这样的useEffect,在组件里面直接调用window.scrollX, window.screenY就可以直接用了。

bash 复制代码
  useEffect(() => {
    function handleScroll(event) {
      console.log(window.screenX, window.screenY);
    }
    window.addEventListener("scroll", handleScroll);
    return () => {
      window.removeEventListener("scroll", handleScroll);
    };
  }, []);

我们自定义一个hook封装一下。

bash 复制代码
import { useEffect, useState } from "react";
function useScrollPosition() {
  const [scrollX, setScrollX] = useState(0);
  const [scrollY, setScrollY] = useState(0);
  useEffect(() => {
    function handleScroll(event) {
      console.log(window.screenX, window.screenY);
      setScrollX(window.scrollX);
      setScrollY(window.scrollY);
    }
    window.addEventListener("scroll", handleScroll);
    return () => {
      window.removeEventListener("scroll", handleScroll);
    };
  }, []);
  return [scrollX, scrollY];
}

export default useScrollPosition;

这样就可以在多个组件里面快速使用了。

bash 复制代码
import { memo, useEffect } from "react";
import "./style.css";
import useScrollPosition from "./hooks/useScrollPosition";

const Home = memo((props) => {
  const [scrollX, scrollY] = useScrollPosition();
  return <div>Home:{scrollY} </div>;
});

const About = memo((props) => {
  const [scrollX, scrollY] = useScrollPosition();
  return <div>About:{scrollY} </div>;
});

const App = memo((props) => {
  const [scrollX, scrollY] = useScrollPosition();
  return (
    <div className="page">
      App:{scrollY}
      <Home />
      <About />
    </div>
  );
});
export default App;

style.css

bash 复制代码
.page {
  height: 2000px;
}

页面滚动的时候,多个组件都可以获取到坐标了。

storage

相关推荐
却尘15 分钟前
Next.js 请求最佳实践 - vercel 2026一月发布指南
前端·react.js·next.js
ccnocare16 分钟前
浅浅看一下设计模式
前端
Lee川19 分钟前
🎬 从标签到屏幕:揭秘现代网页构建与适配之道
前端·面试
Ticnix1 小时前
ECharts初始化、销毁、resize 适配组件封装(含完整封装代码)
前端·echarts
纯爱掌门人1 小时前
终焉轮回里,藏着 AI 与人类的答案
前端·人工智能·aigc
twl1 小时前
OpenClaw 深度技术解析
前端
崔庆才丨静觅1 小时前
比官方便宜一半以上!Grok API 申请及使用
前端
星光不问赶路人1 小时前
vue3使用jsx语法详解
前端·vue.js
天蓝色的鱼鱼1 小时前
shadcn/ui,给你一个真正可控的UI组件库
前端
布列瑟农的星空1 小时前
前端都能看懂的Rust入门教程(三)——控制流语句
前端·后端·rust