State Hook
State Hook:是一个在函数组件中使用的函数(useState),用于在函数组件中的使用状态
useState
-
函数有一个参数,这个参数的值表示状态的默认值
-
函数的返回值是一个数组,该数组一定包含两项
第一项:当前状态的值第二项:改变当前状态的函数
一个函数组件中可以有多个状态,这种做法非常有利于横向切分关注点
注意细节
-
useState最好写到函数的起始位置,便于阅读
-
useState严禁出现在代码块(判断、循环)中
-
useState返回的函数(数组的第二项),引用不变(节约内存空间)
-
使用函数改变数据,若数据和之前的数据完全相等(使用Object.is比较),不会导致重新渲染,以达到优化效率的目的
-
使用函数组件改变数据,传入的值不会和原来的数据进行合并,而是直接替换
-
如果要实现强制刷新
1. 类组件:使用forceUpdate函数 2. 函数组件:使用一个空对象的useState -
如果某些状态之间没有必然的联系,应该分化为不同的状态,而不要合并成一个对象
-
和类组件的状态一样,函数组件中要改变状态可能是异步的(在DOM事件中),多个状态变化会合并以提高效率,此时,不能信任之前的状态,而应该使用回调函数的方式改变状态。如果状态变化要使用到之前的状态,尽量传递函数。(
如这一点不能理解,请理解下面的例子)
export default function App() {
console.log("App render")
const [n, setN] = useState(0); //使用一个状态,该状态的默认值是0
return <div>
<button onClick={() => {
// setN(n - 1);
// setN(n - 1);
setN(prevN => prevN - 1); //传入的函数,在事件完成之后统一运行
setN(prevN => prevN - 1);
}}>-</button>
<span>{n}</span>
<button onClick={() => {
// setN(n + 1) //不会立即改变,事件运行完成之后一起改变
// setN(n + 1) //此时,n的值仍然是0
setN(prevN => prevN + 1); //传入的函数,在事件完成之后统一运行
setN(prevN => prevN + 1);
}}>+</button>
</div>
}
Effect Hook
Effect Hook:用于在函数组件中处理副作用
副作用:
- ajax请求
- 计数器
- 其他异步操作
- 更改真实的DOM对象
- 本地存储
- 其他会对外部产生影响的操作
useEffect:该函数接收一个函数作为参数,接收的函数就是需要进行副作用操作的函数
注意细节
-
副作用函数的运行时间点,是在页面完成真实的UI渲染之后。因此它的执行是异步的,并且不会阻塞浏览器
1. 与类组件中componentDidMount和componentDidUpdate的区别 2. componentDidMount和componentDidUpdate,更改了真实DOM,但是用户还没有看到UI更新,同步的 3. useEffect中的副作用函数,更改了真实DOM,并且用户已经看到了UI更新,异步的 -
每个函数组件中,可以多次使用useEffect,但不要放在判断或者循环等代码块中
-
useEffect中的副作用函数,可以有返回值,返回值必须是一个函数,该函数通常被叫做清理函数
1. 该函数运行时间点,在每次运行副作用函数之前(意思就是说,不管什么时候,只要返回了清理函数,都会在副作用函数运行之前,先运行清理函数) 2. **注意** 清理函数在组件首次渲染的时候不会运行 3. 组件被销毁时,一定会运行 -
useEffect函数,可以传递第二个参数
1. 第二个参数是一个数组 2. 数组中记录了该副作用函数的依赖数据 3. 当组件重新渲染后,只有依赖数据与上次不一样时,才会执行副作用 4. 当传递了依赖数据之后,如果数据没有发生变化 1. 副作用函数仅在第一次渲染后运行 2. 清理函数仅在卸载组件后运行 -
副作用函数中,如果使用了函数上下文中的变量,则由于闭包的影响,会导致副作用函数中变量不会实时变化。
因此尽量避免在副作用函数中使用外部变量 -
副作用函数在每次注册时,会覆盖掉之前的副作用函数,因此,尽量保持副作用函数的稳定性,,否则控制起来会比较复杂
Reducer Hook
Flux:Facebook出品的一个数据流框架
-
规定了数据是单向流动的
-
数据存储在数据仓库中(可以认为state就是一个存储数据的仓库)
-
action是改变数据的唯一原因(本质上就是一个对象,action有两个属性)
- type:字符串,动作的类型
- payload:任意类型,动作发生后的附加信息
-
具体改变数据的是一个函数,该函数叫做reducer
- 该函数接收两个参数
- state:表示当前数据仓库中的数据
- action:描述了如何去改变数据,以及改变数据的一些附加信息
- 该函数必须有一个返回结果,用于表示数据仓库变化之后的数据
- Flux要求,对象是不可变的,如果返回对象,必须创建新的对象
- reducer必须是纯函数,不能有任何副作用
- 该函数接收两个参数
-
如果要触发reducer,不可以直接调用,而是应该调用一个辅助函数dispatch
- 该函数仅接收一个参数:action
- 该函数会间接去调用reducer,以达到改变数据的目的
/**
* 该函数,根据当前的数据,已经存在的action,生成一个新的数据
* @param {*} state
* @param {*} action
*/
function reducer(state, action) {
switch (action.type) {
case "increase":
return state + 1;
case "decrease":
if (state === 0) {
return 0;
}
return state - 1;
default:
return state;
}
}
export default function App() {
const [n, dispatch] = useReducer(reducer, 10, (args) => {
console.log(args)
return 100
});
return (
<div>
<button onClick={() => {
dispatch({ type: "decrease" })
}}>-</button>
<span>{n}</span>
<button onClick={() => {
dispatch({ type: "increase" })
}}>+</button>
</div>
)
}
Context Hook
作用:用获取由React.createContext()函数创建的上下文数据
const ctx = React.createContext();
function Test() {
const value = useContext(ctx);
return <h1>Test,上下文的值:{value}</h1>
}
export default function App() {
return (
<div>
<ctx.Provider value="abc">
<Test />
</ctx.Provider>
</div>
)
}
Callback Hook
useCallback:用于得到一个固定引用值的函数,通常用它进行性能优化
该函数有两个参数:
- 函数,useCallback会固定该函数的引用,只要依赖项没有发生变化,则始终返回之前函数的地址
- 数组,记录依赖项
该函数返回:引用相对固定的函数地址
上面的解释可能对一些人理解起来不太容易,请理解下面的代码示例
// React.PureComponent与React.component的最大的区别在于有没有实现shouldComponentUpdate,该函数会进行组件结构的浅比较,在某些情况下可以提高组件渲染性能
class Test extends React.PureComponent {
render() {
console.log("Test Render")
return <div>
<h1>{this.props.text}</h1>
<button onClick={this.props.onClick}>改变文本</button>
</div>
}
}
function Parent() {
console.log("Parent Render")
const [txt, setTxt] = useState(123)
const [n, setN] = useState(0)
return (
<div>
{/* 函数的地址每次渲染都发生了变化,导致了子组件跟着重新渲染,若子组件是经过优化的组件,则可能导致优化失效 */}
<Test text={txt} onClick={() => {
setTxt(Math.random());
}} />
<input type="number"
value={n}
onChange={e => {
setN(parseInt(e.target.value))
}}
/>
</div>
)
}
export default function App() {
return (
<div>
<Parent />
</div>
)
}
使用useCallback返回的函数在当前作用于的引用不会改变(就可以避免上面的这种情况)
function Parent() {
console.log("Parent Render")
const [txt, setTxt] = useState(1)
const [n, setN] = useState(0)
const handleClick = useCallback(() => {
setTxt(txt + 1)
}, [txt])
return (
<div>
<Test text={txt} onClick={handleClick} />
<input type="number"
value={n}
onChange={e => {
setN(parseInt(e.target.value))
}}
/>
</div>
)
}
Memo Hook
用于保持一些比较稳定的数据,通常用于性能优化
如果React元素本身的引用没有发生变化,一定不会重新渲染
在useMemo函数内通过复杂计算获取当前值得时候,不需要再父组件每次更新的时候重新计算,只要在依赖项发生变化的时候计算即可
Ref Hook
useRef:1. 一个参数(为默认值) 2. 返回一个固定的对象,{current: 值}
通常用于获取React元素的对象,用法类似于vue中ref获取DOM结构的方式
ImperativeHandle Hook
useInperativeHandle:可以让你在使用ref时自定义暴露给父组件的实例值。useImperativeHandle应当与forwardRef一起使用
- 父组件使用useRef(或creatRef)创建一个ref对象,将这个ref对象赋给子组件的ref属性
- 子组件使用forwardRef包装自己,允许作为函数组件的自己使用ref。然后使用useImpreativeHandle钩子函数,在该钩子函数的第二个参数中返回一些状态或方法,这个被返回的状态或方法就可以被父组件访问到
- 父组件使用创建的ref对象的current属性获取子组件暴露出的状态或方法
const FancyInput = React.forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} type="text" />
});
const App = props => {
const fancyInputRef = useRef();
return (
<div>
<FancyInput ref={fancyInputRef} />
<button
onClick={() => fancyInputRef.current.focus()} // 调用子组件的方法
>父组件调用子组件的 focus</button>
</div>
)
}
LayoutEffect Hook
useEffect:浏览器渲染完成后,用户看到新的渲染结果之后 useLayoutEffectHook:完成了DOM改动,但还没有呈现给用户
应该尽量使用useEffect,因为它不会导致渲染阻塞,如果出现了问题,再考虑使用useLayoutEffectHook
并非所有 effect 都可以被延迟执行。例如,一个对用户可见的 DOM 变更就必须在浏览器执行下一次绘制前被同步执行,这样用户才不会感觉到视觉上的不一致。(概念上类似于被动监听事件和主动监听事件的区别。)React 为此提供了一个额外的 useLayoutEffect Hook 来处理这类 effect。它和 useEffect 的结构相同,区别只是调用时机不同。
其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。