List
- 关于 Hooks
- useCallback & useMemo
- 通过 hooks 实现防抖、节流
- useReducer
- ProvidersComposer
- 遗留问题合集
关于 Hooks
React内置的hook提供了基础的能力,虽然本质上它也有一些分层,比如:
- useState是基于useReducer的简化版。
- useMemo和useCallback事实上可以基于useRef实现。
开发过程中注意配合插件 eslint-plugin-react-hooks,指定相关规则 rules-of-hooks、exhaustive-deps
Why there is no callback for setState, useEffect
Because it invokes a thought pattern that may very well be an antipattern with hooks.
With hooks, you are writing code more declarative than imparative.
The thought of “do one thing THEN do another thing THEN do another thing” would be the imparative (here: callback- or promise-based) way.
What actually should be done in the hooks model would rather be a “if this has been rendered, and this property is different than before, X should be done to reach the target state”, which is more declarative. You don’t designate when stuff happens, but just give a condition/dependency and let react pick the right point in time for it.
useCallback & useMemo
useCallback(fn, deps) 相当于 useMemo(() => fn, deps)
useCallback(fn, deps) 返回 fn 这个会根据 deps 变化的回调函数,侧重于函数
而 useMemo(() => fn/computeExpensiveValue(a, b), [a, b]) 侧重于返回值,哪怕你这个返回值实际上是个函数
useMemo 缓存组件,以 PinViewComments 为例,仅在初始拿到 tmpProps 内容后就应该被独立出去
之后 usePinViewHook 中状态的变更不应该影响到 PinViewComments,参考如下:
// before 每次 usePinViewHook 中状态有变更,会导致 PinViewComments 重新加载
const PinViewCommentsLogic = () => {
const { comments, comment_count } = pin;
if (comment_count) {
let tmpProps = {
comments,
comment_count,
pf,
};
console.log('enter PinViewCommentsLogic'); // 这里会在初始化时被打印,之后 usePinViewHook 中状态变更再次打印
return <PinViewComments {...tmpProps} />;
} else {
return null;
}
};
// adxxxxxxxxxxx top
// adxxxxxxxxxxx pin_detail
return (
<div className={pf}>
// ...
<PinViewCommentsLogic />
</div>
);
// after 只在初始后
const PinViewCommentsLogic = useMemo(() => {
const { comments, comment_count } = pin;
if (comment_count) {
let tmpProps = {
comments,
comment_count,
pf,
};
console.log('enter PinViewCommentsLogic'); // 仅在初始化时被打印
return <PinViewComments {...tmpProps} />;
} else {
return null;
}
}, []);
return (
<div className={pf}>
// ...
{/* <PinViewCommentsLogic /> */}
{PinViewCommentsLogic}
);
通过 hooks 实现防抖、节流
// 防抖
useEffect(() => {
const timer = setTimeout(() => {
console.log(a);
}, 2000);
return () => clearTimeout(timer);
}, [a])
function useDebounce(fn, delay, dep = []) {
const { current } = useRef({ fn, timer: null });
useEffect(function () {
current.fn = fn;
}, [fn]);
return useCallback(function f(...args) {
if (current.timer) {
clearTimeout(current.timer);
}
current.timer = setTimeout(() => {
current.fn.call(this, ...args);
}, delay);
}, dep)
}
// 节流
useReducer
function useReducer(reducer, initialState) {
const [state, setState] = useState(initialState);
function dispatch(action) {
const nextState = reducer(state, action);
setState(nextState);
}
return [state, dispatch];
}
ProvidersComposer
const ProviderComposer = ({ providers, children }) => {
return providers.reduceRight((children, parent, index) => React.cloneElement(parent, { children }), children);
};
const ProvidersComposer = (props: any) => {
return (
props.providers.reduceRight((children: any, Parent: any) => (
<Parent>{ children }</Parent>
), props.children)
);
};
遗留问题合集
- 为什么会渲染两次
为什么会渲染两次
class Test extends Components {
render() {
console.log('render');
return <div>Test</div>;
}
}
上面这个说实话我讲不出所以然,改用现在函数式的写法不会渲染两次,再看下面这个:
const Test = () => {
const [f, setF] = useState(false);
let t = useRef(null);
const onClick = () => {
setF(true);
};
console.log('render: ', f);
return (
<div style={{ width: '200px', height: '100px', backgroundColor: '#3fa' }} onClick={onClick} ref={t}>
{f.toString()}
</div>
);
};
这个的第一次点击,将状态 f 从 false 变更成 true,这个 render 还能理解,可以再点击一次,还会打印,之后继续点击,再无反应,这个怎么解释呢?