React Hooks
- 每次render 都有独立的变量和Effects
1. useState
-
vue
-data
data() {
return {
count: 0
}
}
-
react类组件
里面的state
interface CounterStateObj {
count: number
}
export class Counter extends React.Component <any, CounterStateObj>{
constructor(props) {
super(props);
this.state = {
count: 0
};
}
}
hook
function Counter() {
const [count, setCount] = useState<number>(0);
const log = () => {
setTimeout(() => {
console.log('2秒后', count)
}, 2000)
console.log(count)
}
const clickHandler = () => {
log()
setTest(count + 1)
}
return (
<div>
<p>clicked {count} times</p>
<button onClick={clickHandler}>Click me</button>
</div>
);
}
Capture Value
页面 | log函数 |
---|---|
1 | 0 |
2 | 1 |
3 | 2 |
... | ... |
- 类比照相机对着一个杯子拍照,第二张杯子碎了,但第一张照片永远是好的(每次setState就是新拍了一张照片,它不会影响前一个capture的状态)
function Example2() {
const [count, setCount] = useState(0);
const handleClick = () => {
setTimeout(() => {
setCount(count + 1);
}, 3000);
};
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>
setCount
</button>
<button onClick={handleClick}>
Delay setCount
</button>
</div>
);
}
操作
先点击第二个按钮, 然后在3秒内连续点击两次第一个按钮
页面结果
0 -> 1 -> 2 -> 1
问题 --- 接着再重复操作呢?
- 拿到最新的值
setCount(data => data + 1)
2. useRef
- useRef 返回的 ref 对象在组件的整个生命周期内保持不变,也就是说每次重新渲染函数组件时,返回的ref 对象都是同一个(使用 React.createRef ,每次重新渲染组件都会重新创建 ref)
function Counter() {
const count = useRef<number>(0);
const domRef = useRef<HTMLDivElement>()
const clickHandler = () => {
count.current++
}
return (
<div>
<p>You clicked {count.current} times</p>
<div ref='domRef'></div>
<button onClick={clickHandler}>Click me</button>
</div>
);
}
- 访问dom
- vue
<div ref='aa'></div> this.$refs['aa']
- react class
class MyComponent extends React.Component { constructor(props) { super(props); this.myRef = React.createRef(); } render() { return <div ref={this.myRef} />; } }
3. useCallback
- 依赖不变,返回的是同一个函数
const [contentClassName, setContentClassName] = useState<string>('')
const resizeHandler = useCallback(() => {
if (contentClassName) {
setH(contentClassName)
}
}, [contentClassName])
useEffect(() => {
let event: UEventEmitter = Ioc(UEventEmitter)
event.on('fullScreenChange', () => {
resizeHandler()
})
window.addEventListener('resize', resizeHandler)
return () => {
window.removeEventListener('resize', resizeHandler)
event.delete('fullScreenChange')
}
}, [contentClassName, resizeHandler])
4. useEffect
- 只要状态更新,它就会根据传入的依赖项决定是否执行, 可以拿到最新的状态
- react 类组件 里面的
componentDidMount
+componentDidUpdate
- Vue
created
+updated
- 与 componentDidMount 或 componentDidUpdate 不同, useEffect 调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快
// 变化执行
useEffect(() => {
})
// 变化不执行
useEffect(() => {
}, [])
// 只有 test 变化才会执行
useEffect(() => {
return () => {}
}, [test])
5. useMemo
- 类比 Vue 的 computed
- 适合大量计算,缓存值,依赖不变不会重新更新
const [value, setValue] = useState(0);
const increase = useMemo(() => {
if(value > 2) return value + 1;
}, [value]);
- 和
React.memo()
、React.PureComponent
区别?
React.memo()适用函数组件,仅仅浅比较props
React.PureComponent类组件,浅比较props和state,根据结果决定是否重新渲染组件
function Parent({ a, b }) {
// Only re-rendered if `a` changes:
const child1 = useMemo(() => <Child1 a={a} />, [a]);
// Only re-rendered if `b` changes:
const child2 = useMemo(() => <Child2 b={b} />, [b]);
return (
<>
{child1}
{child2}
</>
)
}
6. useContext
- 获取context值
7. useReducer
- 像 redux一样管理数据和行为
8. useLayoutEffect
- 浏览器 layout 之后,painting 之前执行
- 可以获取元素样式
轮子
1.实现类似类组件的生命周期
componentDidMount
export function useOnMount(fn: () => void, destoryCallBack?: () => void) {
useEffect(() => {
fn()
if (destoryCallBack) {
return () => {
destoryCallBack()
}
}
// eslint-disable-next-line
}, [])
}
componentDidUpdate
export function useOnUpdate(fn: () => void, dep?: any[]) {
const ref = useRef({ fn, mounted: false })
ref.current.fn = fn
useEffect(() => {
// 首次渲染不执行
if (!ref.current.mounted) {
ref.current.mounted = true
} else {
ref.current.fn()
}
// eslint-disable-next-line
}, dep)
}
forceUpdate
export function useForceUpdate() {
const [, setValue] = useState(0)
return useCallback(() => {
// 递增state值,强制React进行重新渲染
setValue(val => (val + 1) % (Number.MAX_SAFE_INTEGER - 1))
}, [])
}
2.实现多语言
- 当语言变化的时候 触发一次render
- 根据当前语言返回对应的值
import { store } from '../store/redux'; // redux
import { sysLanguage } from '../config/langulate'; // 语言配置文件
import { useState, useEffect } from 'react';
import { LangValue } from '../interface/common'; // 接口
export function useLanguage() {
const [lang, setLang] = useState<LangValue>(store.getState().user.lang)
useEffect(() => {
let unsubscribe = store.subscribe(() => {
setLang(store.getState().user.lang)
})
return () => {
unsubscribe()
}
// eslint-disable-next-line
}, [])
return [(cn: string) => {
if (cn && sysLanguage[lang] && sysLanguage[lang][cn]) {
return sysLanguage[lang][cn]
} else {
return cn || ''
}
}
]
}
优化
- 大量计算 适用useMemo,
- 为避免子组件的不必要渲染,useMemo + React.memo() 或 React.PureComponent
例1
function Test() {
console.log('test render')
return (
<div className="test">
呵呵呵
</div>
)
}
export function App() {
const [test, setTest] = useState(1)
console.log('app render')
return (
<div className='app-container' onClick={() => setTest(test + 1)}>
{ test }
<Test/>
</div>
)
}
每次点击 App 和 Test 各重新渲染一次
例1优化
function TestTem() {
console.log('test render')
return (
<div className="test">
呵呵呵
</div>
)
}
const Test = React.memo(TestTem)
export function App() {
const [test, setTest] = useState(1)
console.log('app render')
return (
<div className='app-container' onClick={() => setTest(test + 1)}>
{ test }
<Test/>
</div>
)
}
每次点击 只有 App 重新渲染
例2
type ConfigObj = {
name: number
}
function TestTem(props: {
config: ConfigObj
}) {
console.log('test render')
return (
<div className="test">
呵呵呵
{props.config.name}
</div>
)
}
const Test = React.memo(TestTem)
export function App() {
const [test, setTest] = useState(1)
const [nameData, setNameData] = useState(1)
const config: ConfigObj = {
name: nameData
}
const clickHandler = () => {
setTest(test + 1)
// setNameData(nameData + 2)
}
console.log('app render')
return (
<div className='app-container' onClick={clickHandler}>
{ test }
<Test config={config}/>
</div>
)
}
每次点击 App 和 Test 各执行一次
例2优化
type ConfigObj = {
name: number
}
function TestTem(props: {
config: ConfigObj
}) {
console.log('test render')
return (
<div className="test">
呵呵呵
{props.config.name}
</div>
)
}
const Test = React.memo(TestTem)
export function App() {
const [test, setTest] = useState(1)
const [nameData, setNameData] = useState(1)
// 优化
const config: ConfigObj = useMemo(() => {
return {
name: nameData
}
}, [nameData])
const clickHandler = () => {
setTest(test + 1)
// setNameData(nameData + 2) // 放开这句话 会让config 产生变化 Test会重新渲染
}
console.log('app render')
return (
<div className='app-container' onClick={clickHandler}>
{ test }
<Test config={config}/>
</div>
)
}
- 惰性创建昂贵的对象
const aaa = () => {
console.log('喀纳斯')
return 1
}
// 每次render,都会执行一遍
const [test, setTest] = useState<number>(aaa())
// 只会被执行一次
const [test, setTest] = useState<number>(() => aaa())
- 在useEffect里面及时解绑事件
const resizeHandler = useCallback(() => {
if (contentClassName) {
setH(contentClassName)
}
}, [contentClassName])
useEffect(() => {
let event: UEventEmitter = Ioc(UEventEmitter)
event.on('fullScreenChange', () => {
resizeHandler()
})
window.addEventListener('resize', resizeHandler)
return () => {
window.removeEventListener('resize', resizeHandler)
event.delete('fullScreenChange')
}
}, [contentClassName, resizeHandler])
- 通用逻辑抽取为自定义hook
- 复杂的操作和状态变更,适用useReducer
- 避免向下传递回调, 在大型的组件树中,我们推荐的替代方案是通过 context 用
useReducer
往下传一个dispatch
函数:
- 避免向下传递回调, 在大型的组件树中,我们推荐的替代方案是通过 context 用
const TodosDispatch = React.createContext(null);
function TodosApp() {
// 提示:`dispatch` 不会在重新渲染之间变化
const [todos, dispatch] = useReducer(todosReducer);
return (
<TodosDispatch.Provider value={dispatch}>
<DeepTree todos={todos} />
</TodosDispatch.Provider>
);
}
function DeepChild(props) {
// 如果我们想要执行一个 action,我们可以从 context 中获取 dispatch。
const dispatch = useContext(TodosDispatch);
function handleClick() {
dispatch({ type: 'add', text: 'hello' });
}
return (
<button onClick={handleClick}>Add todo</button>
);
}
- 每次render 组件执行两次?
export default function Test () {
const [test, setTest] = useState(1)
console.log('1111')
return (
<div>
{test}
<div onClick={() => setTest(test + 1)}>
23123123
</div>
</div>
)
}
严格模式
不能自动检测到你的副作用,但它可以帮助你发现它们,使它们更具确定性。通过故意重复调用以下函数来实现的该操作
- Hook 会因为在渲染时创建函数而变慢吗?
不会
** 项目结构