一.useState
- useState会帮助我们定义一个 state变量,useState 是一种新方法,它与 class 里面的 this.state 提供的功能完全相同。一般来说,在函数式组件中退出后变量就会”消失”,而 state 中的变量会被 React 保留。
- useState接受唯一一个参数,在第一次组件被调用时使用来作为初始化值(也可以是一个回调函数)。(如果没有传递参数,那么初始化值为undefined)。
const [state, setstate] = useState(() => { return 10; })
- useState返回值是一个数组,我们可以通过数组的解构,来完成赋值会非常方便;
const [counter, setCounter] = useState(0);
// 不会进行累加:
setCount(count + 10);
setCount(count + 10);
// 会进行累加:
setCount((prevCount) => prevCount + 10);
setCount((prevCount) => prevCount + 10);- 注意: 使用useSate定义变量时不能写在判断条件中;
二.useEffect
1. 代替生命周期函数
componentDidMount / componentDidUpdate / componentWillUnmount
2. useEffect的
参数一:执行的回调函数;
参数二: 该useEffect在哪些state发生变化时,才重新执行;(受谁的影响)可以起到优化的作用;
比如: 某些代码我们只是希望执行一次即可,类似于componentDidMount和componentWillUnmount中完成的事情;(比如网
络请求、订阅和取消订阅);第二个参数传: [ ] 即可;
比如:组件更新时,会先执行组件的销毁,再重新的更新组件, 这样就会频繁执行销毁中的操作, 第二个参数传: [ ] 即可;
比如: 同一个组件中有多个数据,但只希望其中的一个数据(count)变化时重新渲染组件, 第二个参数传: [count] 即可;
类组件中:
import React, { PureComponent } from 'react'
export default class ClassCounterTitleChange extends PureComponent {
constructor(props) {
super(props);
this.state = {
counter: 0
}
}
componentDidMount() {
// 1. 组件第一次渲染时执行
// 修改DOM
document.title = this.state.counter;
// 订阅事件
console.log("订阅一些事件");
// 网络请求
}
componentWillUnmount() {
// 3. 组件销毁时执行
console.log("取消事件订阅");
}
componentDidUpdate() {
// 2. 组件更新时执行
document.title = this.state.counter;
}
render() {
return (
<div>
<h2>当前计数: {this.state.counter}</h2>
<button onClick={e => this.setState({counter: this.state.counter + 1})}>+1</button>
</div>
)
}
}
函数式组件中使用hook- useEffect
import React, { useState, useEffect } from 'react'
export default function HookCounterChangeTitle() {
const [counter, setCounter] = useState(0);
useEffect(() => {
// 1. 组件 第一次渲染(相当于componentDidMount);
// 2. 组件更新时执行(相当于componentDidUpdate);
document.title = counter;
},[counter])
useEffect(() => {
// 1. 组件 第一次渲染,比如:订阅事件;
return ()=> {
// 2. 组件销毁时 会执行这个返回值的回调函数(相当于componentWillUnmount),比如:取消事件订阅;
}
},[])
return (
<div>
<h2>当前计数: {counter}</h2>
<button onClick={e => setCounter(counter + 1)}>+1</button>
</div>
)
}
三.useContext
在之前的开发中,我们要在组件中使用共享的Context有两种方式:
- 类组件可以通过 类名.contextType = MyContext方式,在类中获取context;
- 多个Context或者在函数式组件中通过 MyContext.Consumer 方式获取共享context; 但是多个Context共享时的方式会存在大量的嵌套:
- Context Hook允许我们通过Hook来直接获取某个Context的值;
注意:
当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重新渲染,并使用最新传递给 MyContext provider 的 context value 值。
四. useReducer
useReducer仅仅是useState的一种替代方案:
- 在某些场景下,如果state的处理逻辑比较复杂,我们可以通过useReducer来对其进行拆分;
- 或者本次修改的state需要依赖之前的state时,也可以使用;
注意:
很多人看到useReducer的第一反应应该是redux的某个替代品,其实并不是。数据是不会共享的,它们只是使用了相同的counterReducer的函数而已。 所以,useReducer只是useState的一种替代,并不能替代Redux。
五.useContext+useReducer实现Redux
useContext: 实现数据的动态共享;
useReducer: 数据更改逻辑处理,实现数据的更新;
组合在一起就可以实现Redux的效果!!!
实现的效果:
**Color组件中逻辑:**
import React, { createContext, useReducer } from 'react';
export const ColorContext = createContext();
export const UPDATA_COLOR = 'UPDATE_COLOR';
// reducer-处理state数据:
function ColorReducer(state, action) {
switch (action.type) {
case UPDATA_COLOR:
return action.color;
default:
return state;
}
}
export function Color(props) {
const [color, dispatch] = useReducer(ColorReducer, 'yellow');
return (
<div>
// 向所有子组件中共享数据:
<ColorContext.Provider value={{ color, dispatch }}>
{props.children}
</ColorContext.Provider>
</div>
);
}
**用户操作组件中:**
import React, { useContext } from 'react';
import { ColorContext, UPDATA_COLOR } from './03_color';
export default function User() {
// 获取共享的dispatch:
const { dispatch } = useContext(ColorContext);
return (
<div style={{ padding: 20 }}>
<button onClick={() => dispatch({ type: UPDATA_COLOR, color: 'red' })}>
变红色
</button>
<button onClick={() => dispatch({ type: UPDATA_COLOR, color: 'blue' })}>
变蓝色
</button>
</div>
);
}
**使用state数据的组件中:**
import React, { useContext } from 'react';
import { ColorContext } from './03_color';
export default function Home() {
const { color } = useContext(ColorContext);
return <div style={{ color: color }}>我现在是什么颜色</div>;
}
六. useCallback
- useCallback实际的目的是为了进行性能的优化。
- 如何进行性能的优化呢?
2.1 useCallback会返回一个回调函数的 memoized(记忆的) 值;
2.2 在依赖不变的情况下,多次定义的时候,返回的回调函数是相同的;
注意:通常使用useCallback的目的是不希望子组件进行多次渲染,并不是为了函数进行缓存.
七. useMemo
- useCallback实际的目的是为了进行性能的优化。
- 如何进行性能的优化呢?
2.1 useCallback会返回一个回调函数返回值的 memoized(记忆的) 值;
2.2 在依赖不变的情况下,多次定义的时候,回调函数返回的值是相同的;
案例:
案例一:进行大量的计算操作,是否有必须要每次渲染时都重新计算;
案例二:对子组件传递相同内容的对象时,使用useMemo进行性能的优化
八. useRef
- useRef返回一个ref对象,返回的ref对象再组件的整个生命周期保持不变。
- 最常用的ref是两种用法:
用法一:引入DOM(或者组件,但是需要是class组件)元素;
用法二:保存一个数据,这个对象在整个生命周期中可以保存不变;
案例一:引用DOM
案例二:使用ref记录上一次的某一个值
九. useImperativeHandle
先来回顾一下ref和forwardRef结合使用:
- 通过forwardRef可以将ref转发到子组件;
- 子组件拿到父组件中创建的ref,绑定到自己的某一个元素中;
forwardRef的做法本身没有什么问题,但是我们是将子组件的DOM直接暴露给了父组件:
- 直接暴露给父组件带来的问题是某些情况的不可控;父组件可以拿到DOM后进行任意的操作;
- 但是,事实上在上面的案例中,我们只是希望父组件可以操作focus,其他并不希望它随意操作;
通过useImperativeHandle可以值暴露固定的操作:
- 通过useImperativeHandle的Hook,将传入的ref和useImperativeHandle第二个参数返回的对象绑定到了一起;
- 所以在父组件中,使用 inputRef.current时,实际上使用的是返回的对象; 比如我调用了 focus函数,甚至可以调用 hello函数;
十. useLayoutEffect
- useEffect会在渲染的内容更新到DOM上后执行,不会阻塞DOM的更新;
- useLayoutEffect会在渲染的内容更新到DOM上之前执行,会阻塞DOM的更新;
注意:如果我们希望在某些操作发生之后再更新DOM,那么应该将这个操作放到useLayoutEffect;
十一. 自定义hook
自定义Hook本质上只是一种函数代码逻辑的抽取,严格意义上来说,它本身并不算React的特性;
应用: 可以抽离公共逻辑;
注意:自定义hook名要以use开头;
案例演示:
案例一: 所有的组件在创建和销毁时都进行某些相同的操作;
案例二: 组件实时获取滚动的位置;
自定义hook usePosition:
import React, { useState, useEffect } from 'react';
export default function usePosition() {
const [postion, setpostion] = useState(0);
useEffect(() => {
function scrollFn() {
setpostion(window.scrollY);
}
document.addEventListener('scroll', scrollFn);
return () => {
document.removeEventListener('scroll', scrollFn);
};
}, []);
return postion;
}
使用:
import React from 'react';
// 引入自定义的hook:
import usePosition from './hooks/usePosition';
export default function DefineHook() {
return (
<div style={{ height: '200px' }}>
<div style={{ height: '2000px', backgroundColor: 'green' }}>
<h3 style={{ position: 'fixed', top: 0, left: 0 }}>
{/* 直接调用自定义的hook */}
我在滚动,位置{usePosition()}
</h3>
</div>
</div>
);
}