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

相关推荐
一个处女座的程序猿O(∩_∩)O6 分钟前
完成第一个 Vue3.2 项目后,这是我的技术总结
前端·vue.js
mubeibeinv7 分钟前
项目搭建+图片(添加+图片)
java·服务器·前端
逆旅行天涯13 分钟前
【Threejs】从零开始(六)--GUI调试开发3D效果
前端·javascript·3d
m0_7482552635 分钟前
easyExcel导出大数据量EXCEL文件,前端实现进度条或者遮罩层
前端·excel
长风清留扬1 小时前
小程序毕业设计-音乐播放器+源码(可播放)下载即用
javascript·小程序·毕业设计·课程设计·毕设·音乐播放器
web147862107231 小时前
C# .Net Web 路由相关配置
前端·c#·.net
m0_748247801 小时前
Flutter Intl包使用指南:实现国际化和本地化
前端·javascript·flutter
飞的肖1 小时前
前端使用 Element Plus架构vue3.0实现图片拖拉拽,后等比压缩,上传到Spring Boot后端
前端·spring boot·架构
青灯文案11 小时前
前端 HTTP 请求由 Nginx 反向代理和 API 网关到后端服务的流程
前端·nginx·http
m0_748254881 小时前
DataX3.0+DataX-Web部署分布式可视化ETL系统
前端·分布式·etl