📔
饥人谷前端体系课程笔记
黑马程序员笔记前端面试押题前端精进
  • 课程大纲
  • Git入门
    • 3. 软件安装详解
    • 4. Mac环境搭建
    • 5. 命令行入门
    • 6. 本地仓库
    • 7. Git远程仓库-GitHub
  • HTML全解
    • 8. HTML概览
    • 9. HTML标签
    • 10. HTML重难点
    • 11. HTML实践 & 手机调试
  • CSS全解
    • 12. CSS基础
    • 13. CSS布局(上)
    • 14. CSS布局(下)
    • 15. CSS定位
    • 16. CSS动画
  • HTTP全解
    • 17. URL 是什么
    • 18. 请求和响应 & Node.js Server
  • JS全解
    • 19. JavaScript概览
    • 20. 内存图与JS世界(精品课)
    • 21. Canvas 实践—画图板
    • 22. JS语法
    • 23. JS数据类型
    • 24. JS 对象
    • 25. JS 对象分类
    • 26. JS 数组
    • 27. JS 函数
    • 28. JS 实战,会动的代码
    • 29. JS运算符
    • 30. JS总结
  • JS编程接口
    • 31. DOM编程
    • 32. 手写DOM库
    • 33. JQuery中的设计模式(上)
    • 34. JQuery中的设计模式(下)
    • 35. DOM事件与事件委托
  • 项目 前端导航站点
    • 前端导航项目笔记
  • 前后分离
    • 40. AJAX的原理
    • 41. 异步与Promise
    • 42. 跨域、CORS、JSONP
    • 43. 静态服务器
    • 44. AJAX实战:Cookie、Session
  • JS进阶MVC
    • 48. MVC(上)
    • 49. MVC(中)
    • 50. MVC(下)
  • 项目构建
    • 51. Webpack(上)
    • 52. Webpack(下)
  • 算法与数据结构
    • 62. 伪代码与流程图
  • React全解
    • 75. React起手式
    • 76. React类组件和函数组件
    • 77. Class组件详解
    • 78. 函数组件
    • 79. Hooks原理解析
    • 80. Hooks各个击破
    • 81. 精通Redux
  • Node.js
    • 84. Node.js技术架构
    • 85. 文件模块
    • 86. 单元测试之文件模块
    • 87. 调试 Node.js 程序
    • 88. 静态服务器
    • 89. 命令行翻译工具
    • 90. 操作数据库
    • 91. 数据库基础知识
    • 92. Stream 流
    • 93. child_process
    • 94. 总结
  • TypeScript
    • 112. 基础
    • 113. 泛型
  • Next.js全解
    • Next.js 上
    • Next.js 下
  • ES6精讲
    • Promise,async/await
  • 大屏可视化笔记
    • 大屏可视化项目
  • SCSS全解
    • SCSS全解
  • 拓展
    • 一次性弄懂性能优化
    • Web性能优化
由 GitBook 提供支持
在本页
  • useState
  • 使用状态
  • useState续
  • useState接受函数
  • setState接受函数
  • useReducer
  • 用来践行Flux/Redux的思想
  • 一个用useReducer的表单例子
  • 用useReducer代替redux
  • 步骤
  • useContext
  • 上下文
  • 使用方法
  • 注意事项
  • useEffect
  • 副作用(API名字叫得不好。)
  • 用途
  • 特点
  • useLayoutEffect
  • 布局副作用
  • 特点
  • 经验
  • useMemo
  • 特点
  • 注意
  • useCallback
  • 用法
  • useRef
  • 目的
  • 延伸
  • useRef能做到变化时自动render吗?
  • 不想自己加
  • forwardRef
  • useRef
  • forwardRef
  • useImperativeHandle
  • 名字起得稀烂
  • 分析
  • 自定义Hook
  • 封装数据操作
  • 分析
  • stale Closure
  • 总结
  • 方方推荐的JS书籍:

这有帮助吗?

  1. React全解

80. Hooks各个击破

Hooks

上一页79. Hooks原理解析下一页81. 精通Redux

最后更新于3年前

这有帮助吗?

useState

使用状态

const[n, setN] = React.useState(0) <====n是读接口,setN是写接口
const[user,setUser] = React.useState({name:'F'})

注意事项1:不可局部更新

如果state是一个对象,能否部分setState?

答案是不行,请看示例代码

https://codesandbox.io/s/jirengudemo-t16xl?file=/src/App.js:0-365

import React, { useState } from "react";

export default function App() {
  const [user, setUser] = useState({ name: "Frank", age: 18 });
  const onClick = () => {
    setUser({
      name: "Jack"
    });
  };
  return (
    <div className="App">
      <h1>{user.name}</h1>
      <h2>{user.age}</h2>
      <button onClick={onClick}>Click</button>
    </div>
  );
}

会出现像图中这种,只copy了name,而没有copy age,因为因为setState不会帮我们合并属性

如何解决?

setUser({
		...user, <====...user就是把user所有的属性全部copy过来
		name:'Jack'
})

那么useReducer会合并属性吗?妈的,也不会!

注意事项2:地址要变

setState(obj)如果obj地址不变,那么React就认为数据没有变化

 const onClick = () => {
    user.name = "Jack"
    setUser(user);
  };
//这段代码在鼠标点击按钮后不会起任何变化

useState续

useState接受函数

const [state, setState] = useState(()=>{
	
  return initialState

})

该函数返回初始state,且只执行一次

setState接受函数

https://codesandbox.io/s/hopeful-dubinsky-m8gnt?file=/src/index.js:290-350

import React, { useState } from "react";
import ReactDOM from "react-dom";

function App() {
  const [n, setN] = useState(0);
  const onClick = () => {
    //setN(n + 1);
    //setN(n + 1); // 你会发现 n 不能加 2
     setN(i=>i+1) <==============
     setN(i=>i+1)
  };
  return (
    <div className="App">
      <h1>n: {n}</h1>

      <button onClick={onClick}>+2</button>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

什么时候用这种方式?

如果你能接受这种方式,应该优先使用这种形式

useReducer

用来践行Flux/Redux的思想

开代码,分4步走

  1. 创建初始值initialState

  2. 创建所有操作reducer(state,action)

  3. 传给useReducer,得到读和写API

  4. 调用写({type:'操作类型})

总的来说useReducer是useState的复杂版

示例代码

一个用useReducer的表单例子

用useReducer代替redux

步骤

  1. 将数据集中在一个store对象

  2. 将所有操作集中在reducer

  3. 创建一个Context

  4. 创建对数据的读写API

  5. 将第四部的内容放到第三部的Context

  6. 用Context.Provider将Context提供给所有组件

  7. 各个组件用useContext获取读写API

例子:

如何模块化?其实很简单

useContext

上下文

全局变量是全局的上下文。

上下文是局部的全局变量。

使用方法

之前的例子已经介绍过了。

  1. 使用C = createContext(initial)创建上下文。

  2. 使用<C.provider>圈定作用域。

  3. 在作用域内使用useContext(C)来使用上下文。

import { createContext, useContext, useState } from "react";
import "./styles.css";

const C = createContext(null);

export default function App() {
  const [n, setN] = useState(0);
  return (
    <C.Provider value={{ n, setN }}>
      <div className="App">
        <Parents />
      </div>
    </C.Provider>
  );
}

function Parents() {
  return (
    <div>
      我是爸爸
      <Child />
    </div>
  );
}

function Child() {
  const { n, setN } = useContext(C);
  const onClick = () => {
    setN((i) => i + 1);
  };
  return (
    <div>
      我是Child, 我得到的n : {n}
      <button onClick={onClick}>+1</button>
    </div>
  );
}

注意事项

useContext不是响应式的,你在一个模块将C里面的值改变,另一个模块,不会感知到这个变化。

useEffect

副作用(API名字叫得不好。)

useEffect就是在更新后执行某个操作

对环境的改变记为副作用,如修改document.title

但我们不一定非要把副作用放在useEffect里

实际上叫做afterRender更好,每次render后运行。

用途

作为componentDidMount使用,[]做第二个参数,表示只在第一次更新后执行,其他时候不执行。

作为componentDidUpdate使用,可指定依赖

import "./styles.css";
import { useEffect, useState } from "react";
export default function App() {
  const [n, setN] = useState(0);
  const onClick = () => {
    setN((i) => i + 1);
  };
  const afterRender = useEffect;
//case 1
  afterRender(() => {
    console.log("第一次渲染之后执行这句话");
  }, []); //只在第一次渲染之后执行,加个空数组,意思是[]里面的变量变化时执行=》不执行
//case 2
  afterRender(() => {
    console.log("第一二三次执行这句话");//任何一个state变化时都执行,这里没有加第二个参数默认为所有的状态
  });
//case 3
  afterRender(() =>{
    //如果想做到第二次变化才执行
    if(n!== 0)
    console.log("n变化了")//在某个值变化的时候执行这句话,比如这里是当n变化的时候执行
  },[n])
  return (
    <div className="App">
      n:{n}
      <button onClick={onClick}> +1</button>
    </div>
  );
}

作为componentWillUnmount使用,通过return

以上三种用途可同时存在。

componentwillunmount

import "./styles.css";
import { useEffect, useState } from "react";
export default function App() {
  const [n, setN] = useState(0);
  const onClick = () => {
    setN((i) => i + 1);
  };
  useEffect(()=>{
    const id = setInterval(()=>{
      console.log('hi')
    },1000)
    return ()=>{
      window.clearInterval(id)
    }
  })

  return (
    <div className="App">
      n:{n}
      <button onClick={onClick}> +1</button>
    </div>
  );
}

特点

如果同时存在多个useEffect,会按照出现次序执行。

useLayoutEffect

当中途需要改变dom的时候,(中途截胡),但是一般很少用到

import { useEffect, useState } from "react";
import "./styles.css";

export default function App() {
  const [value,setValue] = useState(0);
  useEffect(()=>{
    document.querySelector('#x').innerText = `value:1000`
  },[value])
  return (
    <div id="x" onClick={()=>setValue(0)}>
      value:{value}
    
    </div>
  );
}
import { useLayoutEffect, useState } from "react";
import "./styles.css";

export default function App() {
  const [value,setValue] = useState(0);
  useLayoutEffect(()=>{
    document.querySelector('#x').innerText = `value:1000`
  },[value])
  return (
    <div id="x" onClick={()=>setValue(0)}>
      value:{value}
    
    </div>
  );
}

布局副作用

useEffect在浏览器 渲染完成后执行。

例子

useLayoutEffect 在浏览器渲染前执行。

通过时间点来侧面证明(此处要画图说明)。

特点

useLayoutEffect总是比useEffect先执行

useLayoutEffect里的任务最好影响了Layout

经验

为了用户体验,优先使用useEffect(优先渲染)

useMemo

一句话总结:就是在相应组件的props变化的时候才执行,否则不执行。

要理解React.useMemo需要先讲React.memo,React默认有多余的render。

讲代码中的Child用React.memo(Child)代替。

如果props不变,就没有必要再次执行一个函数组件。

最终效果:代码。

import React, { useState } from "react";
import "./styles.css";

export default function App() {
  const [n, setN] = useState(0);
  const [m, setM] = useState(0);
  const onClick = () => {
    setN(n + 1);
  };
  const onClick2 = ()=>{
    setM(m + 1)
  }
  return (
    <div className="App">
      <button onClick={onClick}> update n {n}</button>
      <button onClick={onClick2}> update m {m}</button>
      <Child2 data={m} />
    </div>
  );
}

function Child(props) {
  console.log("child执行了");
  console.log("假设这里有大量代码");
  return <div>child:{props.data}</div>;
}

const Child2 = React.memo(Child);

memo可以改写成下面这样

const Child = memo((props) => {
  console.log("child执行了");
  console.log("假设这里有大量代码");
  return <div>child:{props.data}</div>;
});

但是这玩意儿有一个bug。

添加了监听函数之后,1秒破功。

因为App 运行时会再次执行第12行,生成新的函数,新旧函数虽然功能一样,但是地址不一样,怎么办,用useMemo

import React, { useState,memo, useMemo } from "react";
import "./styles.css";

export default function App() {
  const [n, setN] = useState(0);
  const [m, setM] = useState(0);
  const onClick = () => {
    setN(n + 1);
  };
  
  const onClickChild = useMemo(()=>{
    return ()=>{
      console.log(m)
    }
  },[m])
  const onClickChild = useCallback(()=>{
    console.log(m);
  },[m])
  
  return (
    <div className="App">
      <button onClick={onClick}> update n {n}</button>
      {/* <button onClick={onClick2}> update m {m}</button> */}
      <Child2 data={m} onClick={onClickChild}/>
    </div>
  );
}

function Child(props) {
  console.log("child执行了");
  console.log("假设这里有大量代码");
  return <div>child:{props.data}</div>;
}

const Child2 = React.memo(Child);

特点

第一个参数是()=>value,见定义

第二个参数是依赖[m,n]。

只有当依赖变化时,才会计算出value

如果依赖不变,那么就重用之前的value

这不就是vue2的computed吗

注意

如果你的value是个函数,那么你就要写成useMemo(()=>(x)=>console.log(x))。

这是一个返回函数的函数。

是不是很难用?于是就有了useCallback。

useCallback

useCallback是useMemo的语法糖

用法

useCallback(x => log(x), [m])等价于useMemo(()=>x =>log(x),[m])

useRef

目的

  • 如果你需要一个值,在组件不断render时保持不变。

  • 初始化: const count = useRef(0)

  • 读取: count.current

  • 为什么需要current?

  • 为了保证两次useRef是同一个值(只有引用能做到)

  • 此处需要画图解释

延伸

  • 看看Vue3的ref

  • 初始化: const count = ref(0)

  • 读取:count.value

  • 不同点: 当count.value变化时,Vue3会自动render

useRef就是用来控制全局变量

useEffect是每次更新之后需要做的操作

下面这段代码用来记录一共更新了几次。

import { useEffect, useRef, useState } from "react";
import "./styles.css";

export default function App() {
  const count = useRef(0);
  const [n, setN] = useState(0);
  const onClick = () => {
    setN(n + 9);
  };
  useEffect(() => {
    count.current += 1;
    console.log(count.current);
  });
  return (
    <div className="App">
      <div>
        <button onClick={onClick}>update n {n}</button>
      </div>
    </div>
  );
}

.current不会更新UI,如果需要render,要手动添加一个setN,调用这个setN(Math.random) 就可以更新UI。

useRef能做到变化时自动render吗?

不能,为什么不能,因为这不符合React的理念,React的理念是UI=f(data),你如果想要这个功能,完全可以自己加,监听ref,当ref.current变化时,调用setX即可。

不想自己加

那你就用Vue3吧,Vue3帮你加好了。

总结

useState/useReducer(useState的复杂版)n每次都变

useMemo/useCallback [m]的时候fn会变

useRef 永远不变

forwardRef

总结:forwardRef就是让你在函数组件上加了一个参数,然后就可以使用ref,class组件默认就可以用ref。

讲了useRef就不得不讲一下它了。

基本用法:让函数组件Button2支持ref

代码一:props无法传递ref属性

代码二:实现ref的传递

import { forwardRef ,useRef} from "react";
import "./styles.css";

export default function App() {
  const buttonRef = useRef(null);
  return (
    <div className="App">
      <Button3 ref={buttonRef}>按钮</Button3>
    </div>
  );
}
const Button2 = (props,ref) =>{
  console.log(props);
  console.log(ref)
  return <button className="red" ref={ref} {...props} />
}

const Button3 = forwardRef(Button2)

useRef

可以用来引用DOM对象。

也可以用来引用普通对象。

forwardRef

由于props不包含ref,所以需要forwardRef。

为什么props不包含ref呢?因为大部分时候不需要。

useImperativeHandle

名字起得稀烂

应该叫做setRef。

不用useImperativeHandle的代码

import {
  forwardRef,
  useEffect,
  useImperativeHandle,
  useRef,
  createRef
} from "react";
import "./styles.css";

export default function App() {
  const buttonRef = useRef(null);
  useEffect(() => {
    console.log(buttonRef.current);
  });
  return (
    <div className="App">
      <Button2 ref={buttonRef}>按钮</Button2>
      <button
        className="close"
        onClick={() => {
          console.log(buttonRef);
          buttonRef.current.x();
        }}
      >
        {" "}
        x
      </button>
    </div>
  );
}

const Button2 = forwardRef((props,ref) =>{
const realButton = createRef(null);
useImperativeHandle(ref,() => ({
     x:() => {
      realButton.current.remove();
     }
   }))
 })

用了useImperativeHandle的代码。

import {
  forwardRef,
  useEffect,
  useImperativeHandle,
  useRef,
  createRef
} from "react";
import "./styles.css";

export default function App() {
  const buttonRef = useRef(null);
  useEffect(() => {
    console.log(buttonRef.current);
  });
  return (
    <div className="App">
      <Button2 ref={buttonRef}>按钮</Button2>
      <button
        className="close"
        onClick={() => {
          console.log(buttonRef);
          buttonRef.current.x();
        }}
      >
        {" "}
        x
      </button>
    </div>
  );
}


const Button2 = forwardRef((props, ref) => {
  const realButton = createRef(null);
  const setRef = useImperativeHandle;
  setRef(ref, () => {
    return {
      x: () => {
        realButton.current.remove();
      }
    };
  });
  return <button ref={realButton} {...props} />;
});

分析

用于自定义ref的属性。

自定义Hook

react里面最牛逼的功能。可以添加增删改查的功能,类似于把接口暴露给使用者,使用者只要知道怎么用即可,不用管内部怎么运作的。自定义Hooks的时候可以使用Context,reducer,这样都没有必要使用redux。方方建议在自己的项目中尽量使用自定义Hook。

封装数据操作

简单例子

贴心例子

分析

你还可以在自定义Hook里使用Context。

useState只说了不能在if里,没说不能在函数里运行,只要这个函数在函数组件里运行即可。

stale Closure

总结

React Hooks

  • useState状态

  • useEffect副作用=======>useLayoutEffect

    • useEffect就是afterRender

    • useLayoutEffect就是比useEffect提前一点点,很少用的原因是会影响渲染效果,特殊情况下会用

  • useContext上下文

    • 就是开放一个读写接口给整个页面用

  • Redux useReducer

    • useReducer是专门给Redux的用户设计的,可以不用redux

  • useMemo记忆=======>useCallback回调

    • useMemo要和React.memo配合使用

    • 如果useMemo不好用,可以使用useCallback

  • useRef引用 ======>useImperativeHandle

    • useRef就是保持一个量不变

    • forwardRef不是hook

    • useImperativeHandle 就是setRef,在支持ref的时候可以自定义ref

  • 自定义Hook =====> useDebugValue

    • useDebugValue用的比较少,自己看文档

方方提到最常用的React Hooks

  • useState

  • useReducer

  • useMemo

用得比较少的React Hooks

  • useRef

  • useContext

方方推荐的JS书籍:

代码示例:

代码三: 两次ref传递得到button的引用。 这段代码是实现拖拽组件

代码:

过时的闭包,参考文章链接

image-20220403234634817

你不知道的JavaScript

https://codesandbox.io/s/loving-tesla-l47snp?file=/src/App.js:0-626
https://codesandbox.io/s/useeffect-z6g8jl?file=/src/App.js
https://codesandbox.io/s/uselayouteffect-gb1pqx?file=/src/App.js
https://codesandbox.io/s/usememo-7f0qfj?file=/src/App.js
https://codesandbox.io/s/forwardref-18q9d9?file=/src/App.js:0-403
https://codesandbox.io/s/amazing-snow-9f5g3
https://codesandbox.io/s/useimperativehandle-ye0d4q?file=/src/App.js
https://codesandbox.io/s/wizardly-tesla-sy077
https://codesandbox.io/s/recursing-darkness-zuo4b
https://dmitripavlutin.com/react-hooks-stale-closures/
https://u1lib.org/book/3635107/5efe33
Screen Shot 2021-12-20 at 5.00.41 AM
jrg-demo01