Hook 是 React16.8 的新特性。可以在不编写 class 的情况下使用 state 以及其它 React 特性。可以使你在无需修改组件结构的情况下复用状态逻辑
为什么要使用 hooks
- hooks 可以使代码的逻辑性更强,可以抽离公共方法,公共组件
- hooks 可以用函数声明方式代替 class 声明方式,函数即组件
- hooks 可能把 class 组件拆分成小组件。
主要的 hooks
- userCallback
- useContext
- useEffect
- useLayoutEffect
- useMemo
- useReducer
- useRef
- useState
hooks 的使用
只能在函数最外层调用 hook。不要在循环、条件判断或者子函数中调用 只能在函数组件中使用 hook
useState 数据存储,派发更新
state/props 改变, 组件重新渲染(父组件变化->内部的所有子组件都要重新渲染) useState 每次渲染,函数都会重新执行。函数执行完毕,所有的内存都会释放掉。在函数内部创建一个当前函数组件的装填,提供了一个修改状态的方法. useState 只有首次渲染的时候才会执行赋值初始值。再次执行获取的不是初始值,而是闭包中的缓存值
import React, { useState } from 'react';
const DemoState = () => {
let [count, setCount] = useState(0);
const handleAdd = () => {
count++;
// count可以是任意值, state的改变都是异步的
setCount(count);
};
return (
<div>
<h1>我是hook组件</h1>
<p>state→{count}</p>
<button onClick={handleAdd}>加1</button>
</div>
);
};
export default DemoState;
useState 初始化时可以传入一个回调函数, setCount一样可以传入一个回调函数
const num = 2;
let [count, setCount] = useState(() => {
return num * 10;
});
setCount(() => ++count);
useEffect 副作用操作
副作用 → 没有发生在数据向视图转换过程中的逻辑,分为需要清除的,和不需要清除的 函数组件,纯函数,props 这些固定的输入总会得到固定的输出。
- useEffect 是 componentDidMound、componentDidUpdate 和 componentWillUnmount 的组合
- useEffect(fn) 接受一个函数,fn 再组件渲染到屏幕之后才会执行。如果有返回值,则返回一个清除副作用的函数,否则不返回
- 一般是不需要同步执行的,不会阻塞浏览器的渲染。如果需要同步执行 可以使用 useLayoutEffect
import React, { useState, useEffect } from 'react';
const DemoHook = () => {
const num = 2;
let [count, setCount] = useState(() => {
return num * 10;
});
const handleAdd = () => {
setCount(() => ++count);
};
// Dom渲染完成之后才会执行
useEffect(() => {
setTimeout(() => {
setCount((count) => ++count);
}, 1000);
});
return (
<div>
<h1>我是hook组件</h1>
<p>state→{count}</p>
<button onClick={handleAdd}>加1</button>
</div>
);
};
export default DemoHook;
运行上面代码之后会发现,数字在不断的自动更新
- dom 渲染完成后,副作用执行(useEffect 回调执行)
- 副作用执行过程中,修改了 count,state 状态被修改,从而引发了 dom 的重新渲染,继而副作用执行
- 形成了无限循环 解决这种现象可以向 useEffect 传入第二个参数[],数组中定义了组件重新渲染需要的 state 值,只有当数组中 state 值改变了,useEffect 才会执行。如果只传了一个空数组,则在组件重新渲染后,useEffect 不会再执行,可以用来优化 如果我们需要再组件销毁的阶段,做一些取消事件监听或清除定时器等操作时,再 useEffect 函数第一个参数返回一个函数用于清除这些副作用,相当于 componentWillUnmount. 清除副作用函数的时机:
- 在组件的 unmount 之前
- 如果有多个 useEffect 要执行,则在下一个 useEffect 执行前
useLayoutEffect 渲染更新之前的 useEffect
- useEffect 执行顺序: 组件更新挂载完成 → 渲染完成 →useEffect 回调执行
- useLayoutEffect 执行顺序: 组件更新挂载完成 → 执行 useLayoutEffect 回调 → 页面渲染 useLayoutEffect 会阻止页面渲染,引起页面卡顿
useContext自由获取 context
我们可以使用 useContext 来获取父级组件传递过来的 context 值,这个当前值就是最近的父级组件 Provider 设置的 value 的值,useContext 参数一般是由 createContext 方式引入。useContext 可以代替 context.Consumer 来获取 Provider 中保存的 value 值
- 创建provider.tsx
import React, { useState, createContext, Context } from 'react';
export interface ContextValue {
count: number;
setCount: Function;
add: Function;
reduce: Function;
}
export const context: Context<any> = createContext({});
export const ContextProvider = (props: any) => {
let [count, setCount] = useState(10);
const countVal = {
count,
setCount,
add: () => setCount(count + 1),
reduce: () => setCount(count - 1)
};
const { children } = props;
return <context.Provider value={countVal}>{children}</context.Provider>;
};
- 创建subContext.tsx
import React, { useContext } from 'react';
import { context, ContextProvider } from './contextDemo';
const Context1 = () => {
const { count = 0, add, reduce } = useContext(context);
return (
<div>
<p>{count}</p>
<button onClick={() => add()}>加1</button>
<button onClick={() => reduce()}>减1</button>
</div>
);
};
const subContext = () => (
<ContextProvider>
<Context1 />
</ContextProvider>
);
export default subContext;
useReducer无状态组件中的 redux
- useReducer接受的第一个参数是函数,我们可以认为这是一个 reducer,reducer 的参数就是常规的 state 和 action,返回改变后的 state。
- useReducer第二个参数为 state 的初始值。
- useReducer第三个参数为以 state 为参数的回调函数,用来修改初始值
- useReducer返回一个数组,数组的第一项就是更新之后的 state 的值,第二个参数是派发更新的 dispatch 函数
- dispatch 函数会触发组件的更新
能够促使组件重新渲染的一个useState派发更新函数, 另一个就是 useReducer 中的 dispatch。
import React, { useReducer } from 'react';
interface State {
count: number;
num: number;
}
interface Action {
type: string;
value?: any;
}
const initalState: State = {
count: 0,
num: 1
};
const reducer = (state: State, action: Action) => {
switch (action.type) {
case 'add':
return { ...state, count: state.count + 1 };
case 'reduce':
return { ...state, count: state.count - 1 };
default:
return state;
}
};
const ReducerCom = () => {
const [state, dispatch] = useReducer(reducer, initalState);
return (
<div>
<p>{state.count}</p>
<button onClick={() => dispatch({ type: 'add' })}>加一</button>
</div>
);
};
export default ReducerCom;
高阶组件
import React, { useReducer, FunctionComponent, Context, createContext } from 'react';
export const ProviderContext: Context<any> = createContext({});
// eslint-disable-next-line react/display-name
const HOCComponent = (reducer: any, initState: any) => (Com: FunctionComponent) => {
// eslint-disable-next-line react/display-name
return () => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const [state, dispatch] = useReducer(reducer, initState);
return (
<ProviderContext.Provider value={{ state, dispatch }}>
<Com />
</ProviderContext.Provider>
);
};
};
export default HOCComponent;
高阶组件使用
import React, { useContext } from 'react';
import concat, { ProviderContext } from './HOCComponent';
export const initState = {
count: 0
};
export const reducer = (state: any, action: Record<string, any>) => {
switch (action.type) {
case 'add':
return { ...state, count: state.count + 1 };
default:
return state;
}
};
const ReducerCom2 = () => {
const { state, dispatch } = useContext(ProviderContext);
return (
<div>
<p>{state.count}</p>
<button onClick={() => dispatch({ type: 'add' })}>加1</button>
</div>
);
};
export default concat(reducer, initState)(ReducerCom2);
useRef获取元素,缓存数据
useRef 有一个参数可以作为缓存数据的初始值,返回值可以被 dom 元素 ref 标记,可以获取被标记的元素节点。
import React, { useRef, useState } from 'react';
const RefDemo = () => {
const [value, setValue] = useState('');
const inputDom = useRef<HTMLInputElement | null>(null);
const handlerSubmit = () => {
const $input = inputDom.current;
console.log($input);
console.log($input?.value);
};
const reset = () => {
setValue('');
};
const inputChange = () => {
const $input = inputDom.current;
if ($input) {
setValue($input.value);
}
};
console.log(11);
return (
<div>
<input type='text' ref={inputDom} value={value} onChange={() => inputChange()} />
<button onClick={() => handlerSubmit()}>提交</button>
<button onClick={() => reset()}>重置</button>
</div>
);
};
export default RefDemo;
useMemo
- useMemo能形成独立的渲染空间,能够使组件、变量按照约定好的规则更新
- useMemo渲染条件依赖于第二个参数 deps(参考 useEffect)
- useMemo可以避免不必要的更新好不必要的上下文的执行
import React, { useMemo, useState } from 'react';
const useMemoDemo = () => {
const [list, setList] = useState([0, 1, 2]);
const [number, setNum] = useState(0);
const [count, setCount] = useState(0);
const changeList = () => {
const a = [...list];
a.push(a.length);
setList(a);
};
// list和number任何一个被改变后useMemo会被重新执行,count被改变后useMemo不会执行
// 被useMemo包裹的count没有改变,。必须等到useMemo被重新执行后才能获取到最新的count
const lists = useMemo(() => {
console.log('又被执行了😔');
return (
<div>
<ul>
{list.map((item) => {
console.log('list被渲染了🍺');
return <li key={item}>{item}</li>;
})}
</ul>
<p>number→{number}</p>
<span>count→{count}</span>
</div>
);
}, [list, number]);
return (
<div>
<div>
<span>count→{count}</span>
<button onClick={() => setCount(count + 1)}>count+1</button>
</div>
<div>
<span>number→{number}</span>
<button onClick={() => setNum(number + 1)}>count+1</button>
</div>
{lists}
<button onClick={() => changeList()}>修改</button>
</div>
);
};
export default useMemoDemo;
useCallback useMemo版本的回调函数
- useCallback和useMemo接受的参数都是一样,都是再其依赖项发生变化后才执行,都返回缓存的值
- 区别在于useMemo返回的是函数运行的结果,useCallback返回的是函数。
import React, { useCallback, ComponentProps, useState, useEffect } from 'react';
const SubCom = (props: ComponentProps<any>) => {
console.log('被更新了🚗');
useEffect(() => {
props.logInfo('被渲染了');
}, []);
return <div>sub</div>;
};
const CallbackDemo = () => {
const [count, setCount] = useState(0);
const logInfo = useCallback((subName: string) => {
console.log(subName);
}, []);
return (
<div>
<span>count→{count}</span>
<button onClick={() => setCount(count + 1)}>加一</button>
<SubCom logInfo={logInfo} />
</div>
);
};
export default CallbackDemo;