传入初始值,返回一个state以及更新state的函数。若初始值为函数,函数只在初始渲染时被调用
模拟实现useState(利用数组存储state和setter,闭包存储指针)
let state = [];
let setters = [];
let firstRun = true;
let cursor = 0;
function createSetter(cursor) {
return function setterWithCursor(newVal) {
state[cursor] = newVal;
};
}
// This is the pseudocode for the useState helper
export function useState(initVal) {
if (firstRun) {
state.push(initVal);
setters.push(createSetter(cursor));
firstRun = false;
}
const setter = setters[cursor];
const value = state[cursor];
cursor++;
return [value, setter];
}
与 class 组件中的 setState 方法不同,useState 不会自动合并更新对象。你可以用函数式的 setState 结合展开运算符来达到合并更新对象的效果。
setState(prevState => {
// 也可以使用 Object.assign
return {...prevState, ...updatedValues};
})
useReducer 是另一种可选方案,它更适合用于管理包含多个子值的 state 对象。
函数组件渲染到屏幕之后执行,可以只有某些值改变之后执行
(React只会在浏览器绘制后运行effects。这使得你的应用更流畅因为大多数effects并不会阻塞屏幕的更新。Effect的清除同样被延迟了。上一次的effect会在重新渲染后被清除)
useEffect(() => {
//每次render后执行
return () => {
//当前 effect 之前对上一个 effect 进行清除
}
})
useEffect(() => {
//仅在第一次render后执行
return () => {
//组件卸载前执行
}
}, [])
useEffect(() => {
//arr变化后,render后执行
return () => {
//下一useEffect运行前执行
}
}, arr)
const [value, setValue] = useState()
useEffect(() => {
const timer1 = setTimeout(() => {
console.log('send')
console.log(value)
}, 2000)
return () => {
console.log('clearLastSend')
clearTimeout(timer1)
}
}, [value])
<Input value={value} placeholder="input here" onInput={e => setValue(e.target.value)} />
import React, { useState, useEffect } from 'react';
import axios from 'axios';
function App() {
const [data, setData] = useState({ hits: [] })
const [query, setQuery] = useState(initialQuery)
useEffect( () => {
//不要直接使用async函数,useEffect期待返回清除函数而不是promise
//const result = await axios(
//'http://hn.algolia.com/api/v1/search?query=redux',
//);
//setData(result.data);
const fetchData = async () => {
const result = await axios(
`http://hn.algolia.com/api/v1/search?query=${query}`
)
setData(result.data);
}
fetchData();
}, [query]);
return (
...
}
export default App;
上面的代码注释处有一个问题。我们使用了 async/await 来处理异步操作,根据规范,async 函数会返回一个隐式的 Promise。然而 effect hook 要么什么都不返回,要么返回一个清理函数。因此,运行注释的代码,你会在 console 里看到这样的警告:Warning: useEffect function must return a cleanup function or nothing. Promises and useEffect(async () => …) are not supported, but you can call an async function inside an effect. 因此,不能直接给 useEffect 传一个 async 函数,我们需要在 useEffect 内部,定义一个单独的 async 函数。
每个hook存放在组件对应的fiberNode对应的memoizedState中,以链表的形式存储,第一个hook的next指向下一个hook,以此类推。每个hook还有自己的memoizedState,useState的memoizedState存储的就是最新值,useEffect的memoizedState是个单向循环链表,存储的是一个个(下次渲染会使用到的)effect对象,有属性tag,用于区分是否要执行改useEffect。useEffect的执行分mount和update两种,因为mount阶段的useEffect都会执行,所以mount阶段的effect都会被打上HookHasEffect的tag标记。之后每次创建新的effect的时候都会比较当前deps和前一次是否发生改变,如果改变,打上HookHasEffect的tag标记,若没变,打上HookTagEffect的tag标记。
tag就是一个二进制数,用来标记effect,从而决定它的行为。以下是 ReactSideEffectTags.js 与 ReactHookEffectTags.js 中的定义。
export const NoEffect = /* / 0b00000000;
export const UnmountSnapshot = / / 0b00000010;
export const UnmountMutation = / / 0b00000100;
export const MountMutation = / / 0b00001000;
export const UnmountLayout = / / 0b00010000;
export const MountLayout = / / 0b00100000;
export const MountPassive = / / 0b01000000;
export const UnmountPassive = / */ 0b10000000;
使得函数组件也能使用context
Context 全局数据,不需逐层传递
const ct = createContext(defaultValue)
const GrandFather = () => {
return <ct.Provider value={'grandsonName'}><Father /></ct.Provider>
}
const Father = () => {
return <Son />
}
const Son = () => {
return <Grandson />
}
const Grandson = () => {
const name = useContext(ct)
return <div>{name}</div>
}
传入一个回调函数和依赖项(数组),依赖项变化时回调
useCallback(callback,depArray)
&useMemo(()=>{callback},depArray)
useCallback返回该回调函数的 memoized 版本,在依赖参数不变的情况下,返回的回调函数是同一个引用地址,仅在某个依赖项改变时回调函数才会更新(地址变化)。用于避免自组件的非必要性渲染。
useMemo返回一个 memoized 值,在依赖参数不变的的情况返回的是上次第一次计算的值。优化针对于当前组件高开销的计算。
1 获取DOM元素的节点
2 获取子组件的实例
3 渲染周期之间共享数据的存储(state不能存储跨渲染周期的数据,因为state的保存会触发组件重渲染)
createRef 每次渲染都会返回一个新的引用,而 useRef 每次都会返回相同的引用
如果想要返回最新的引用,在useEffect中赋值即可
import React, { useState, useEffect, useRef } from "react";
function App() {
const [count, setCount] = useState(0);
// 把定时器设置成全局变量使用useRef挂载到current上
const timer = useRef();
// 首次加载useEffect方法执行一次设置定时器
useEffect(() => {
timer.current = setInterval(() => {
setCount(count => count + 1);
}, 1000);
}, []);
// count每次更新都会执行这个副作用,当count > 5时,清除定时器
useEffect(() => {
if (count > 5) {
clearInterval(timer.current);
}
});
return <h1>count: {count}</h1>;
}
export default App
const [state, dispatch] = useReducer(reducer, initialState)
const [state, dispatch] = useReducer(reducer, initialArg, init)
传入reducer&初始值,返回新值&dispatch
把state的更新逻辑迁移到effect之外,在reducer中对状态进行管理,根据diapatch的不同行为返回新的state。
这么做可以将用于计算 state 的逻辑提取到 reducer 外部
使用useReducer的场景
• 如果你的state是一个数组或者对象
• 如果你的state变化很复杂,经常一个操作需要修改很多state
• 如果你想将数据处理和展示分隔开
共享逻辑的方案:render props 使用值为函数的prop 来共享代码/高阶组件/自定义hooks
两个函数的共享逻辑,将其提取到第三个函数中。
两个函数组件的共享逻辑,提取到一个自定义hook中,名称以use开头,hook内部顶层调用其他hook
function useInterval(callback, delay) {
const lastestCallback = useRef(() => {})
useEffect(() => {
lastestCallback.current = callback
})
useEffect(() => {
if (delay !== null) {
const interval = setInterval(() => lastestCallback.current(), delay || 0)
return () => clearInterval(interval)
}
return undefined
}, [delay])
}
useInterval(() => setAge(age - 1), 1000)
useInterval(() => setName(name + 'y'), 2000)
1 use开头…
2 顶层使用(不在判断、循环、嵌套函数中使用):react靠hooks的调用顺序,来表示不同的hooks
hooks存储在fiber树中组件对应的fiberNode的memoizedState中,以链表的形式有序存储,之后被react机械地读取。
mobx hooks
mobx相关hooks:https://mobx-react.js.org/observer-hook
import { useObserver, useLocalStore } from 'mobx-react' // 6.x or [email protected]
function Person() {
const person = useLocalStore(() => ({ name: 'John' }))
return useObserver(() => (
<div>
{person.name}
<button onClick={() => (person.name = 'Mike')}>No! I am Mike</button>
</div>
))
}
useObserver
返回DOM的地方使用useOberver,一旦其中observable数据变动,整个组件重新render
useLocalStore
执行传入的函数,创建组件的store(for a lifetime of a component),将对象变为observable ,getter变为computed,方法变为actions
useRoutes:https://blog.logrocket.com/how-react-hooks-can-replace-react-router/
mobx-react-lite : https://github.com/mobxjs/mobx-react-lite
collections of hooks :https://nikgraf.github.io/react-hooks/
storybook : https://beizhedenglong.github.io/react-hooks-lib/