React官网:https://zh-hans.reactjs.org/docs/hooks-reference.html#useeffect
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
setState
函数用于更新 state
。它接收一个新的 state
值并将组件的一次重新渲染加入队列。state
完全相同,则随后的重渲染会被完全跳过。class
组件中的 setState
方法不同,useState
不会自动合并更新对象。你可以用函数式的 setState
结合展开运算符来达到合并更新对象的效果。// 返回一个 state,以及更新 state 的函数(可变)
// 在初始渲染期间,返回的状态 (state) 与传入的第一个参数 (initialState) 值相同
const [state, setState] = useState(initialState);
// 传一个新的state过去
setState(newState);
setState(prevState => {
// 也可以使用 Object.assign
return {...prevState, ...updatedValues};
});
effect
将在每轮渲染结束后执行,但你可以选择让它 在只有某些值改变的时候才执行。useEffect(didUpdate);
effect
// useEffect 函数需返回一个清除函数
useEffect(() => {
const subscription = props.source.subscribe();
return () => {
// 清除订阅
subscription.unsubscribe();
};
});
为防止内存泄漏,清除函数会在组件卸载前执行。另外,如果组件多次渲染(通常如此),则在执行下一个
effect
之前,上一个effect
就已被清除。
useEffect
传递第二个参数,它是 effect
所依赖的值数组。只有当 data
改变后才会重新创建订阅。useEffect(
() => {
console.log("组件挂载完之后执行");
return () => {
console.log("组件即将卸载时执行");
};
},
[],
);
useEffect(
() => {
const subscription = data.get();
return () => {
subscription.delete();
};
},
[data],
);
useEffect(()=>{
console.log("组件挂载完成之后及更新完成之后执行");
})
const isMounted = useRef(false);
useEffect(()=>{
if(isMounted.current){
console.log("组件更新完成")
} else {
isMounted.current = true;
}
})
effect
(仅在组件挂载和卸载时执行),可以传递一个空数组([]
)作为第二个参数。这就告诉 React 你的 effect
不依赖于 props
或 state
中的任何值,所以它永远都不需要重复执行。这并不属于特殊情况 —— 它依然遵循输入数组的工作方式。冷洪林:https://www.jianshu.com/p/cc91178724d5
在Hooks
出来之前,开发者都是使用的class
组件,通过props
传值。现在使用方法组件(Function
)开发了,没有constructor
构造函数也就没有了props
的接收,所以父子组件的传值就成了一个问题。React Hooks
就为我们准备了useContext
来解决这个问题。
import React, { useState, createContext, useContext } from "react";
const CountContext = createContext(0);
const Example = () => {
const [count, setCount] = useState<number>(0);
return (
<div>
<p>父组件点击数量:{count}</p>
<button onClick={() => setCount(count + 1)}>{"点击+1"}</button>
<CountContext.Provider value={count}>
<Counter />
</CountContext.Provider>
</div>
);
};
const Counter = () => {
const count = useContext(CountContext);
return <p>子组件获得的点击数量:{count}</p>;
};
export default Example;
const [state, dispatch] = useReducer(reducer, initialArg, init);
useState
的替代方案。它接收一个形如 (state, action) => newState
的 reducer
,并返回当前的 state
以及与其配套的 dispatch
方法。(如果你熟悉 Redux 的话,就已经知道它如何工作了。)useReducer
会比 useState
更适用,例如 state
逻辑较复杂且包含多个子值,或者下一个 state
依赖于之前的 state
等。并且,使用 useReducer
还能给那些会触发深更新的组件做性能优化,因为你可以向子组件传递 dispatch
而不是回调函数 。https://www.jianshu.com/p/09b85595407e
import { useReducer } from "react";
const initState = {
num: 0,
};
interface initStateType {
num: number;
}
interface actionType {
type: string;
}
const numReducer = (state: initStateType, action: actionType) => {
switch (action.type) {
case "add":
return { ...state, num: state.num + 1 };
case "reduce":
return { ...state, num: state.num - 1 };
default:
return state;
}
};
const Example = () => {
const [state, dispatch] = useReducer(numReducer, initState);
return (
<div style={{ textAlign: "center", marginTop: 50 }}>
<div>数量: {state.num}</div>
<button onClick={() => dispatch({ type: "reduce" })}>减少</button>
<button onClick={() => dispatch({ type: "add" })}>增加</button>
</div>
);
};
export default Example;
有助于性能改善的,有 2 种场景:
useEffect
,又或者是配合React.Memo
使用const Child = React.memo(function({val, onChange}) {
console.log('render...');
return <input value={val} onChange={onChange} />;
});
function App() {
const [val1, setVal1] = useState('');
const [val2, setVal2] = useState('');
const onChange1 = useCallback( evt => {
setVal1(evt.target.value);
}, []);
const onChange2 = useCallback( evt => {
setVal2(evt.target.value);
}, []);
return (
<>
<Child val={val1} onChange={onChange1}/>
<Child val={val2} onChange={onChange2}/>
</>
);
}
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
useMemo
,它仅会在某个依赖项改变时才重新计算 memoized
值。这种优化有助于避免在每次渲染时都进行高开销的计算。useMemo
的函数会在渲染期间执行。请不要在这个函数内部执行不应该在渲染期间内执行的操作,诸如副作用这类的操作属于 useEffect
的适用范畴,而不是 useMemo
。useMemo
在每次渲染时都会计算新的值。你可以把 useMemo 作为性能优化的手段,但不要把它当成语义上的保证。
const refContainer = useRef(initialValue);
useRef
返回一个可变的 ref
对象,其 .current
属性被初始化为传入的参数(initialValue
)。返回的 ref
对象在组件的整个生命周期内持续存在。function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` 指向已挂载到 DOM 上的文本输入元素
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
useRef
就像是可以在其 .current
属性中保存一个可变值的“盒子”。https://blog.csdn.net/WJLcomeon/article/details/123246152
useImperativeHandle(ref, createHandle, [deps])
useImperativeHandle
可以让你在使用 ref
时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用 ref
这样的命令式代码。useImperativeHandle
应当与 forwardRef
一起使用:useImperativeHandle
的Hook
, 将父组件传入的ref
和useImperativeHandle
第二个参数返回的对象绑定到了一起inputRef.current
时, 实际上是返回的对象// 子组件
const show = useCallback((data: any) => {
setData(data);
formRef.setFieldsValue(data);
setVisible(true);
}, []);
useImperativeHandle<ImperativeHandleProps, ImperativeHandleProps>(
actionRef,
() => ({
show,
})
);
// 父组件
<CreateAndEditModal
actionRef={modalRef}
onSuccessCallback={() => console.log("刷新列表")}
/>
const handleAdd = useCallback(() => {
modalRef.current?.show();
}, []);
总结:
ref
属性ref.current
调用该对象中的方法forwardRef
的做法本身没有什么问题, 但是我们是将子组件的DOM
直接暴露给了父组件:
DOM
后进行任意的操作focus
,其他并不希望它随意操作其他方法其函数签名与 useEffect
相同,但它会在所有的 DOM
变更之后同步调用 effect
。可以使用它来读取 DOM
布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect
内部的更新计划将被同步刷新。尽可能使用标准的 useEffect
以避免阻塞视觉更新。
const deferredValue = useDeferredValue(value);
该 hook
与使用防抖和节流去延迟更新的用户空间 hooks
类似。使用 useDeferredValue
的好处是,React
将在其他工作完成(而不是等待任意时间)后立即进行更新,并且像 startTransition
一样,延迟值可以暂停,而不会触发现有内容的意外降级。
useDeferredValue
仅延迟你传递给它的值。如果你想要在紧急更新期间防止子组件重新渲染,则还必须使用 React.memo
或 React.useMemo
记忆该子组件
function Typeahead() {
const query = useSearchQuery('');
const deferredQuery = useDeferredValue(query);
// Memoizing 告诉 React 仅当 deferredQuery 改变,
// 而不是 query 改变的时候才重新渲染
const suggestions = useMemo(() =>
<SearchSuggestions query={deferredQuery} />,
[deferredQuery]
);
return (
<>
<SearchInput query={query} />
<Suspense fallback="Loading results...">
{suggestions}
</Suspense>
</>
);
}