react hook 总结
build后可以看到每次就只执行一次了
使用条件
只能在顶层使用, 不能嵌套, 也不能在条件判断和循环中使用, 会直接报错
这是由于hook内部记录状态时使用的链表, 由于判断会打乱调用顺序, 循环不能保证每次调用的次数是一致的, 所以都是被禁止的
useState
在更新state时不会自动合并对象, 不像setState可以通过传递第二个参数来在状态更新之后做一些事情, 尽量使用多个小状态而不是一个大状态
简单计数器
import React, { useState } from "react";
import { inc, applyTo } from "ramda";
const B = () => {
const [count, setCount] = useState(0);
return (
<div>
{count} <button onClick={() => applyTo(inc, setCount)}>inc</button>
</div>
);
};
export default B;
useState 内部使用useReducer实现, 是一个简单的语法糖
import React, { useReducer } from "react";
import { inc } from "ramda";
const useState = (initState) => {
return useReducer(
(state, action) => (typeof action === "function" ? action(state) : action),
typeof initState === "function" ? initState() : initState
);
};
const B = () => {
const [count, setCount] = useState(0);
return (
<div>
{count} <button onClick={() => setCount(inc)}></button>
</div>
);
};
export default B;
useReducer
一个”比较”复杂的计数器
import React, { useReducer } from "react";
import { inc, dec, cond, equals, negate,always } from "ramda";
const reducer = (state, action) =>
cond([
[equals("inc"), always(inc(state))],
[equals("dec"), always(dec(state))],
[equals("negate"), always(negate(state))],
])(action);
const B = () => {
const [count, dispatch] = useReducer(reducer, 0);
return (
<div>
{count}
<button onClick={() => dispatch("inc")}>inc</button>
<button onClick={() => dispatch("dec")}>dec</button>
<button onClick={() => dispatch("negate")}>negate</button>
</div>
);
};
export default B;
useMemo
类似vue的computed, 但是依赖数组需要自己写
import React, { useState, useMemo } from "react";
import { multiply, inc } from "ramda";
const B = () => {
const [count, setCount] = useState(0);
const double = useMemo(() => multiply(2, count), [count]);
return (
<div>
count:{count} double:{double}
<button onClick={() => setCount(inc)}>inc</button>
</div>
);
};
export default B;
使用useMemo实现useRef和useCallback
useCallback 相当于useMemo返回一个函数,
useRef通过传递一个空依赖数组, 保证返回的是一个引用不变对象
const useRef = (value) =>
useMemo(
() => ({
current: value,
}),
[]
);
const useCallback = (cb, deps) => useMemo(() => cb, deps);
使用useRef实现useMemo
import React, { useRef, useState } from "react";
import { multiply,inc } from "ramda";
const useMemo = (getter, deps = []) => {
const depsRef = useRef(deps);
const valueRef = useRef(getter());
if (deps && deps === depsRef.current) return valueRef.current;
depsRef.current = deps;
return (valueRef.current = getter());
};
const B = () => {
const [count, setCount] = useState(0);
const double = useMemo(() => multiply(2, count), [count]);
return (
<div>
count:{count} double:{double}
<button onClick={() => setCount(inc)}>inc</button>
</div>
);
};
export default B;
useCallback
useMemo的语法糖, 但是有很多小问题
经典的由于闭包导致异步函数拿不到最新的值, 此时需要使用useRef保存最新值
import React, { useState } from "react";
import { inc, applyTo, curry } from "ramda";
const B = () => {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(inc);
setTimeout(() => {
console.log(count);
});
};
return (
<div>
{count} <button onClick={handleClick}>inc</button>
</div>
);
};
export default B;
useEffect 和 useLayoutEffect
两者的执行时机不一样
useEffect在已经渲染完毕后执行
useLayoutEffect在dom挂载完毕, 渲染之前执行
下面这段代码, 会改变两个div中的文字, 第一个会出现闪烁, 因为先渲染了短文字, 后渲染了长文字, 第二个不会出现闪烁, 因为短文字并没有被渲染, 也就没有了视觉误差
import React, { useEffect, useLayoutEffect } from "react";
const useMount = (effect) => {
return useEffect(effect, []);
};
const useBeforeMount = (effect) => {
return useLayoutEffect(effect, []);
};
const B = () => {
useMount(() => {
document.getElementById("app1").innerText = "hello";
});
useBeforeMount(() => {
document.getElementById("app2").innerText = "hello";
});
return (
<div>
<div id="app1">B</div> <br/> <div id="app2">B</div>
</div>
);
};
export default B;
useRef
用于保存无关渲染的状态, 改变ref的值并不会触发渲染
import React, { useState, useRef } from "react";
import { inc, applyTo, curry } from "ramda";
const B = () => {
const [count, setCount] = useState(0);
const countRef = useRef(count);
countRef.current = count;
const handleClick = () => {
setCount(inc);
setTimeout(() => {
console.log(count, countRef.current);
});
};
return (
<div>
{count} <button onClick={handleClick}>inc</button>
</div>
);
};
export default B;
useContext
当你准备使用这个hook时其实你需要的是redux….
useImperativeHandle 和 forwardRef
用于暴露指定的接口, ref可以直接暴露dom, 这给了父组件太大的权限, 使其可以任意操作dom, 但是我们也可以只暴露一个操作方法, 比如focus, 父组件只能通过调用这个方法来操作子组件
import React, {
useState,
useRef,
useEffect,
forwardRef,
useImperativeHandle,
} from "react";
import { inc, dec } from "ramda";
const Count = forwardRef((props, ref) => {
const [count, setCount] = useState(0);
return <div ref={ref}>{count}</div>;
});
const CountLess = forwardRef((props, ref) => {
const divRef = useRef(null);
const [count, setCount] = useState(0);
useImperativeHandle(ref, () => ({
inc: ()=>setCount(inc),
dec: ()=>setCount(dec),
}));
return <div ref={divRef}>{count}</div>;
});
const B = () => {
const countRef = useRef(null);
const countLessRef = useRef(null);
const incHandler = () => {
console.log("inc");
console.log(countRef.current, countLessRef.current);
// console.log(countRef.current.inc, countLessRef.current.inc);
countLessRef.current.inc()
};
const decHandler = () => {
console.log("dec");
console.log(countRef.current, countLessRef.current);
// console.log(countRef.current.inc, countLessRef.current.inc);
countLessRef.current.dec()
};
return (
<div>
<Count ref={countRef}></Count>
<CountLess ref={countLessRef}></CountLess>
<button onClick={incHandler}>inc</button>
<button onClick={decHandler}>dec</button>
</div>
);
};
export default B;