React Hooks 使用指南
.markdown-body { line-height: 1.75; font-weight: 400; font-size: 16px; overflow-x: hidden; color: rgba(37, 41, 51, 1) }
.markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4, .markdown-body h5, .markdown-body h6 { line-height: 1.5; margin-top: 35px; margin-bottom: 10px; padding-bottom: 5px }
.markdown-body h1 { font-size: 24px; line-height: 38px; margin-bottom: 5px }
.markdown-body h2 { font-size: 22px; line-height: 34px; padding-bottom: 12px; border-bottom: 1px solid rgba(236, 236, 236, 1) }
.markdown-body h3 { font-size: 20px; line-height: 28px }
.markdown-body h4 { font-size: 18px; line-height: 26px }
.markdown-body h5 { font-size: 17px; line-height: 24px }
.markdown-body h6 { font-size: 16px; line-height: 24px }
.markdown-body p { line-height: inherit; margin-top: 22px; margin-bottom: 22px }
.markdown-body img { max-width: 100% }
.markdown-body hr { border-top: 1px solid rgba(221, 221, 221, 1); border-right: none; border-bottom: none; border-left: none; margin-top: 32px; margin-bottom: 32px }
.markdown-body code { border-radius: 2px; overflow-x: auto; background-color: rgba(255, 245, 245, 1); color: rgba(255, 80, 44, 1); font-size: 0.87em; padding: 0.065em 0.4em }
.markdown-body code, .markdown-body pre { font-family: Menlo, Monaco, Consolas, Courier New, monospace }
.markdown-body pre { overflow: auto; position: relative; line-height: 1.75 }
.markdown-body pre>code { font-size: 12px; padding: 15px 12px; margin: 0; word-break: normal; display: block; overflow-x: auto; color: rgba(51, 51, 51, 1); background: rgba(248, 248, 248, 1) }
.markdown-body a { text-decoration: none; color: rgba(2, 105, 200, 1); border-bottom: 1px solid rgba(209, 233, 255, 1) }
.markdown-body a:active, .markdown-body a:hover { color: rgba(39, 91, 140, 1) }
.markdown-body table { display: inline-block !important; font-size: 12px; width: auto; max-width: 100%; overflow: auto; border: 1px solid rgba(246, 246, 246, 1) }
.markdown-body thead { background: rgba(246, 246, 246, 1); color: rgba(0, 0, 0, 1); text-align: left }
.markdown-body tr:nth-child(2n) { background-color: rgba(252, 252, 252, 1) }
.markdown-body td, .markdown-body th { padding: 12px 7px; line-height: 24px }
.markdown-body td { min-width: 120px }
.markdown-body blockquote { color: rgba(102, 102, 102, 1); padding: 1px 23px; margin: 22px 0; border-left: 4px solid rgba(203, 203, 203, 1); background-color: rgba(248, 248, 248, 1) }
.markdown-body blockquote:after { display: block; content: "" }
.markdown-body blockquote>p { margin: 10px 0 }
.markdown-body ol, .markdown-body ul { padding-left: 28px }
.markdown-body ol li, .markdown-body ul li { margin-bottom: 0; list-style: inherit }
.markdown-body ol li .task-list-item, .markdown-body ul li .task-list-item { list-style: none }
.markdown-body ol li .task-list-item ol, .markdown-body ol li .task-list-item ul, .markdown-body ul li .task-list-item ol, .markdown-body ul li .task-list-item ul { margin-top: 0 }
.markdown-body ol ol, .markdown-body ol ul, .markdown-body ul ol, .markdown-body ul ul { margin-top: 3px }
.markdown-body ol li { padding-left: 6px }
.markdown-body .contains-task-list { padding-left: 0 }
.markdown-body .task-list-item { list-style: none }
@media (max-width: 720px) { .markdown-body h1 { font-size: 24px } .markdown-body h2 { font-size: 20px } .markdown-body h3 { font-size: 18px } }
.markdown-body pre, .markdown-body pre>code.hljs { color: rgba(51, 51, 51, 1); background: rgba(248, 248, 248, 1) }
.hljs-comment, .hljs-quote { color: rgba(153, 153, 136, 1); font-style: italic }
.hljs-keyword, .hljs-selector-tag, .hljs-subst { color: rgba(51, 51, 51, 1); font-weight: 700 }
.hljs-literal, .hljs-number, .hljs-tag .hljs-attr, .hljs-template-variable, .hljs-variable { color: rgba(0, 128, 128, 1) }
.hljs-doctag, .hljs-string { color: rgba(221, 17, 68, 1) }
.hljs-section, .hljs-selector-id, .hljs-title { color: rgba(153, 0, 0, 1); font-weight: 700 }
.hljs-subst { font-weight: 400 }
.hljs-class .hljs-title, .hljs-type { color: rgba(68, 85, 136, 1); font-weight: 700 }
.hljs-attribute, .hljs-name, .hljs-tag { color: rgba(0, 0, 128, 1); font-weight: 400 }
.hljs-link, .hljs-regexp { color: rgba(0, 153, 38, 1) }
.hljs-bullet, .hljs-symbol { color: rgba(153, 0, 115, 1) }
.hljs-built_in, .hljs-builtin-name { color: rgba(0, 134, 179, 1) }
.hljs-meta { color: rgba(153, 153, 153, 1); font-weight: 700 }
.hljs-deletion { background: rgba(255, 221, 221, 1) }
.hljs-addition { background: rgba(221, 255, 221, 1) }
.hljs-emphasis { font-style: italic }
.hljs-strong { font-weight: 700 }
React Hooks
Hook 是什么
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
Hook 是 React 团队在 React 16.8 版本中提出的新特性,在遵循函数式组件的前提下,为已知的 React 概念提供了更直接的 API:props,state,context,refs 以及声明周期,目的在于解决常年以来在 class 组件中存在的各种问题,实现更高效的编写 react 组件
class 组件的不足
难以复用组件间状态逻辑:组件状态逻辑的复用,需要 props render和高阶组件等解决方案,但是此类解决方案的抽象封装将会导致层级冗余,形成“嵌套地狱”
难以维护复杂组件:
- 许多不相干的逻辑代码被混杂在同一个生命周期中,相关联的逻辑代码被拆分到不同声明周期当中,容易遗忘导致产生bug
- 组件常常充斥着状态逻辑的访问和处理,不能拆分为更小的粒度,可通过状态管理库集中管理状态,但耦合了状态管理库又会导致组件复用性降低
this 指向问题:在 JavaScript 中,class 的方法默认不会绑定 this,当调用 class 的方法时 this 的值为 undefined,为了在方法中访问 this 则必须在构造器中绑定或使用 class fields 语法(实验性语法)
class Example extends React.Component {
constructor(props) {
...
// 方式1: 在构造函数中绑定 this
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({...})
}
// 方式2: 使用 class fields 语法
handleClick = () => {
this.setState({...})
}
}
难以对 class 进行编译优化:由于 JavaScript 历史设计原因,使用 class 组件会让组件预编译过程中变得难以进行优化,如 class 不能很好的压缩,并且会使热重载出现不稳定的情况
Hook 的优势
- Hook 使你在无需改变组件结构的情况下复用状态逻辑(自定义 Hook)
- Hook 将组件中互相关联的部分拆分成更小的函数(比如设置订阅或请求数据)
- Hook 使你在非 class 的情况下可以使用更多的 React 特性
Hook 使用规则
Hook 就是 Javascript 函数,使用它们时有两个额外的规则:
- 只能在函数外层调用 Hook,不要在循环、条件判断或者子函数中调用
- 只能在 React 的函数组件和自定义 Hook 中调用 Hook。不要在其他 JavaScript 函数中调用
在组件中 React 是通过判断 Hook 调用的顺序来判断某个 state 对应的 useState
的,所以必须保证 Hook 的调用顺序在多次渲染之间保持一致,React 才能正确地将内部 state 和对应的 Hook 进行关联
useState
useState
用于在函数组件中调用给组件添加一些内部状态 state,正常情况下纯函数不能存在状态副作用,通过调用该 Hook 函数可以给函数组件注入状态 state
useState
唯一的参数就是初始 state,会返回当前状态和一个状态更新函数,并且 useState
返回的状态更新函数不会把新的 state 和旧的 state 进行合并,如需合并可使用 ES6 的对象结构语法进行手动合并
const [state, setState] = useState(initialState);
方法使用
import React, { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count - 1)}>-</button>
<input type="text" value={count} onChange={(e) => setCount(e.target.value)} />
<button onClick={() => setCount(count + 1)}>+</button>
</div>
);
}
等价 class 示例
useState
返回的状态类似于 class 组件在构造函数中定义 this.state
,返回的状态更新函数类似于 class 组件的 this.setState
import React from 'react';
export default class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
render() {
return (
<div>
<button onClick={() => this.setState({ count: this.state.count - 1 })}>-</button>
<input type="text" value={this.state.count} onChange={(e) => this.setState({ count: e.target.value })} />
<button onClick={() => this.setState({ count: this.state.count + 1 })}>+</button>
</div>
);
}
}
函数式更新
如果新的 state 需要通过使用先前的 state 计算得出,可以往 setState
传递函数,该函数将接收先前的 state,并返回一个更新后的值
import React, { useState } from 'react'
export default function Counter() {
const [count, setCount] = useState(0);
const lazyAdd = () => {
setTimeout(() => {
// 每次执行都会最新的state,而不是使用事件触发时的state
setCount(count => count + 1);
}, 3000);
}
return (
<div>
<p>the count now is {count}</p>
<button onClick={() => setCount(count + 1)}>add</button>
<button onClick={lazyAdd}>lazyAdd</button>
</div>
);
}
惰性初始 state
如果初始 state 需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只会在初始渲染时被调用
import React, { useState } from 'react'
export default function Counter(props) {
// 函数只在初始渲染时执行一次,组件重新渲染时该函数不会重新执行
const initCounter = () => {
console.log('initCounter');
return { number: props.number };
};
const [counter, setCounter] = useState(initCounter);
return (
<div>
<button onClick={() => setCounter({ number: counter.number - 1 })}>-</button>
<input type="text" value={counter.number} onChange={(e) => setCounter({ number: e.target.value})} />
<button onClick={() => setCounter({ number: counter.number + 1 })}>+</button>
</div>
);
}
跳过 state 更新
调用 State Hook 的更新函数时,React 将使用 Object.is
来比较前后两次 state,如果返回结果为 true,React 将跳过子组件的渲染及 effect 的执行
import React, { useState } from 'react';
export default function Counter() {
console.log('render Counter');
const [counter, setCounter] = useState({
name: '计时器',
number: 0
});
// 修改状态时传的状态值没有变化,则不重新渲染
return (
<div>
<p>{counter.name}: {counter.number}</p>
<button onClick={() => setCounter({ ...counter, number: counter.number + 1})}>+</button>
<button onClick={() => setCounter(counter)}>++</button>
</div>
);
}
useEffect
在函数组件主体内(React 渲染阶段)改变 DOM、添加订阅、设置定时器、记录日志以及执行其他包含副作用的操作都是不被允许的,因为这可能会产生莫名其妙的 bug 并破坏 UI 的一致性
useEffect
Hook 的使用则是用于完成此类副作用操作。useEffect
接收一个包含命令式、且可能有副作用代码的函数
useEffect
函数会在浏览器完成布局和绘制之后,下一次重新渲染之前执行,保证不会阻塞浏览器对屏幕的更新
useEffect(didUpdate);
方法使用
import React, { useState, useEffect } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
// useEffect 内的回调函数会在初次渲染后和更新完成后执行
// 相当于 componentDidMount 和 componentDidUpdate
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>count now is {count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
</div>
);
}
等价 class 示例
useEffect
Hook 函数执行时机类似于 class 组件的 componentDidMount
、componentDidUpdate
生命周期,不同的是传给 useEffect
的函数会在浏览器完成布局和绘制之后进行异步执行
import React from 'react';
export default class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}
componentDidMount() {
document.title = `You clicked ${this.state.count} times`;
}
componentDidUpdate() {
document.title = `You clicked ${this.state.count} times`;
}
render() {
return (
<div>
<p>count now is {this.state.count}</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>+</button>
</div>
);
}
}
清除 effect
通常情况下,组件卸载时需要清除 effect 创建的副作用操作,useEffect
Hook 函数可以返回一个清除函数,清除函数会在组件卸载前执行。组件在多次渲染中都会在执行下一个 effect 之前,执行该函数进行清除上一个 effect
清除函数的执行时机类似于 class 组件componentDidUnmount
生命周期,这的话使用 useEffect
函数可以将组件中互相关联的部分拆分成更小的函数,防止遗忘导致不必要的内存泄漏
import React, { useState, useEffect } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('start an interval timer')
const timer = setInterval(() => {
setCount((count) => count + 1);
}, 1000);
// 返回一个清除函数,在组件卸载前和下一个effect执行前执行
return () => {
console.log('destroy effect');
clearInterval(timer);
};
}, []);
return (
<div>
<p>count now is {count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
</div>
);
}
优化 effect 执行
默认情况下,effect 会在每一次组件渲染完成后执行。useEffect
可以接收第二个参数,它是 effect 所依赖的值数组,这样就只有当数组值发生变化才会重新创建订阅。但需要注意的是:
- 确保数组中包含了所有外部作用域中会发生变化且在 effect 中使用的变量
- 传递一个空数组作为第二个参数可以使 effect 只会在初始渲染完成后执行一次
import React, { useState, useEffect } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新
return (
<div>
<p>count now is {count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
</div>
);
}
useContext
Context 提供了一个无需为每层组件手动添加 props ,就能在组件树间进行数据传递的方法,useContext
用于函数组件中订阅上层 context 的变更,可以获取上层 context 传递的 value
prop 值
useContext
接收一个 context 对象(React.createContext
的返回值)并返回 context 的当前值,当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider>
的 value
prop 决定
const value = useContext(MyContext);
方法使用
import React, { useContext, useState } from 'react';
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
// 为当前 theme 创建一个 context
const ThemeContext = React.createContext();
export default function Toolbar(props) {
const [theme, setTheme] = useState(themes.dark);
const toggleTheme = () => {
setTheme(currentTheme => (
currentTheme === themes.dark
? themes.light
: themes.dark
));
};
return (
// 使用 Provider 将当前 props.value 传递给内部组件
<ThemeContext.Provider value={{theme, toggleTheme}}>
<ThemeButton />
</ThemeContext.Provider>
);
}
function ThemeButton() {
// 通过 useContext 获取当前 context 值
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<button style={{background: theme.background, color: theme.foreground }} onClick={toggleTheme}>
Change the button's theme
</button>
);
}
等价 class 示例
useContext(MyContext)
相当于 class 组件中的 static contextType = MyContext
或者 <MyContext.Consumer>
useContext
并没有改变消费 context 的方式,它只为我们提供了一种额外的、更漂亮的、更漂亮的方法来消费上层 context。在将其应用于使用多 context 的组件时将会非常有用
import React from 'react';
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
const ThemeContext = React.createContext(themes.light);
function ThemeButton() {
return (
<ThemeContext.Consumer>
{
({theme, toggleTheme}) => (
<button style={{background: theme.background, color: theme.foreground }} onClick={toggleTheme}>
Change the button's theme
</button>
)
}
</ThemeContext.Consumer>
);
}
export default class Toolbar extends React.Component {
constructor(props) {
super(props);
this.state = {
theme: themes.light
};
this.toggleTheme = this.toggleTheme.bind(this);
}
toggleTheme() {
this.setState(state => ({
theme:
state.theme === themes.dark
? themes.light
: themes.dark
}));
}
render() {
return (
<ThemeContext.Provider value={{ theme: this.state.theme, toggleTheme: this.toggleTheme }}>
<ThemeButton />
</ThemeContext.Provider>
)
}
}
优化消费 context 组件
调用了 useContext
的组件都会在 context 值变化时重新渲染,为了减少重新渲染组件的较大开销,可以通过使用 memoization 来优化
假设由于某种原因,您有 AppContext
,其值具有 theme
属性,并且您只想在 appContextValue.theme
更改上重新渲染一些 ExpensiveTree
- 方式1: 拆分不会一起更改的 context
function Button() {
// 把 theme context 拆分出来,其他 context 变化时不会导致 ExpensiveTree 重新渲染
let theme = useContext(ThemeContext);
return <ExpensiveTree className={theme} />;
}
- 当不能拆分 context 时,将组件一分为二,给中间组件加上
React.memo
function Button() {
let appContextValue = useContext(AppContext);
let theme = appContextValue.theme; // 获取 theme 属性
return <ThemedButton theme={theme} />
}
const ThemedButton = memo(({ theme }) => {
// 使用 memo 尽量复用上一次渲染结果
return <ExpensiveTree className={theme} />;
});
- 返回一个内置
useMemo
的组件
function Button() {
let appContextValue = useContext(AppContext);
let theme = appContextValue.theme; // 获取 theme 属性
return useMemo(() => {
// The rest of your rendering logic
return <ExpensiveTree className={theme} />;
}, [theme])
}
useReducer
useReducer
作为 useState
的代替方案,在某些场景下使用更加适合,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。
使用 useReducer
还能给那些会触发深更新的组件做性能优化,因为父组件可以向自组件传递 dispatch 而不是回调函数
const [state, dispatch] = useReducer(reducer, initialArg, init);
方法使用
import React, { useReducer } from 'react'
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
export default function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
初始化 state
useReducer 初始化 sate 的方式有两种
// 方式1
const [state, dispatch] = useReducer(
reducer,
{count: initialCount}
);
// 方式2
function init(initialClunt) {
return {count: initialClunt};
}
const [state, dispatch] = useReducer(reducer, initialCount, init);
useRef
useRef
用于返回一个可变的 ref 对象,其 .current
属性被初始化为传入的参数(initialValue
)
useRef
创建的 ref 对象就是一个普通的 JavaScript 对象,而 useRef()
和自建一个 {current: ...}
对象的唯一区别是,useRef
会在每次渲染时返回同一个 ref 对象
const refContainer = useRef(initialValue);
绑定 DOM 元素
使用 useRef
创建的 ref 对象可以作为访问 DOM 的方式,将 ref 对象以 <div ref={myRef} />
形式传入组件,React 会在组件创建完成后会将 ref 对象的 .current
属性设置为相应的 DOM 节点
import React, { useRef } from 'react'
export default function FocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
绑定可变值
useRef
创建的 ref 对象同时可以用于绑定任何可变值,通过手动给该对象的.current
属性设置对应的值即可
import React, { useState, useRef, useEffect } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
const currentCount = useRef();
// 使用 useEffect 获取当前 count
useEffect(() => {
currentCount.current = count;
}, [count]);
const alertCount = () => {
setTimeout(() => {
alert(`Current count is: ${currentCount.current}, Real count is: ${count}`);
}, 3000);
}
return (
<>
<p>count: {count}</p>
<button onClick={() => setCount(count + 1)}>Count add</button>
<button onClick={alertCount}>Alert current Count</button>
</>
);
}
性能优化(useCallback & useMemo)
useCallback
和 useMemo
结合 React.Memo
方法的使用是常见的性能优化方式,可以避免由于父组件状态变更导致不必要的子组件进行重新渲染
useCallback
useCallback
用于创建返回一个回调函数,该回调函数只会在某个依赖项发生改变时才会更新,可以把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染的子组件,在 props 属性相同情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果
import React, { useState, useCallback } from 'react';
function SubmitButton(props) {
const { onButtonClick, children } = props;
console.log(`${children} updated`);
return (
<button onClick={onButtonClick}>{children}</button>
);
}
// 使用 React.memo 检查 props 变更,复用最近一次渲染结果
SubmitButton = React.memo(submitButton);
export default function CallbackForm() {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
const handleAdd1 = () => {
setCount1(count1 + 1);
}
// 调用 useCallback 返回一个 memoized 回调,该回调在依赖项更新时才会更新
const handleAdd2 = useCallback(() => {
setCount2(count2 + 1);
}, [count2]);
return (
<>
<div>
<p>count1: {count1}</p>
<SubmitButton onButtonClick={handleAdd1}>button1</SubmitButton>
</div>
<div>
<p>count2: {count2}</p>
<SubmitButton onButtonClick={handleAdd2}>button2</SubmitButton>
</div>
</>
)
}
useCallback(fn, deps)
相当于 useMemo(() => fn, deps)
,以上 useCallback
可替换成 useMemo
结果如下:
const handleAdd2 = useMemo(() => {
return () => setCount2(count2 + 1);
}, [count2]);
useMemo
把“创建”函数和依赖项数组作为参数传入 useMemo
,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算
使用注意:
- 传入
useMemo
的函数会在渲染期间执行,不要在这个函数内部执行与渲染无关的操作 - 如果没有提供依赖项数组,
useMemo
在每次渲染时都会计算新的值
import React, { useState, useMemo } from 'react';
function counterText({ countInfo }) {
console.log(`${countInfo.name} updated`);
return (
<p>{countInfo.name}: {countInfo.number}</p>
);
}
// // 使用 React.memo 检查 props 变更,复用最近一次渲染结果
const CounterText = React.memo(counterText);
export default function Counter() {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
const countInfo1 = {
name: 'count1',
number: count1
};
// 使用 useMemo 缓存最近一次计算结果,会在依赖项改变时才重新计算
const countInfo2 = useMemo(() => ({
name: 'count2',
number: count2
}), [count2]);
return (
<>
<div>
<CounterText countInfo={countInfo1} />
<button onClick={() => setCount1(count1 + 1)}>Add count1</button>
</div>
<div>
<CounterText countInfo={countInfo2} />
<button onClick={() => setCount2(count2 + 1)}>Add count2</button>
</div>
</>
);
}
其他 Hook
useImperativeHandle
useImperativeHandle
可以让你在使用 ref
时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle
应当与 React.forwardRef
一起使用:
import React, { useRef, useImperativeHandle, useState } from 'react'
function FancyInput(props, ref) {
const inputRef = useRef();
// 自定义暴露给父组件的 ref 实例值
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} type="text" {...props} />;
}
// 通过 forwardRef 向父组件传递暴露的 ref
const ForwardFancyInput = React.forwardRef(FancyInput);
export default function Counter() {
const [text, setText] = useState('');
const inputRef = useRef();
const onInputFocus = () => {
inputRef.current.focus();
};
return (
<>
<ForwardFancyInput ref={inputRef} value={text} onChange={e => setText(e.target.value)} />
<button onClick={onInputFocus}>Input focus</button>
</>
);
}
useLayoutEffect
useLayoutEffect
与 useEffect
类似,与 useEffect
在浏览器 layout 和 painting 完成后异步执行 effect 不同的是,它会在浏览器布局 layout 之后,painting 之前同步执行 effect
useLayoutEffect
的执行时机对比如下:
import React, { useState, useEffect, useLayoutEffect } from 'react';
export default function LayoutEffect() {
const [width, setWidth] = useState('100px');
// useEffect 会在所有 DOM 渲染完成后执行 effect 回调
useEffect(() => {
console.log('effect width: ', width);
});
// useLayoutEffect 会在所有的 DOM 变更之后同步执行 effect 回调
useLayoutEffect(() => {
console.log('layoutEffect width: ', width);
});
return (
<>
<div id='content' style={{ width, background: 'red' }}>内容</div>
<button onClick={() => setWidth('100px')}>100px</button>
<button onClick={() => setWidth('200px')}>200px</button>
<button onClick={() => setWidth('300px')}>300px</button>
</>
);
}
// 使用 setTimeout 保证在组件第一次渲染完成后执行,获取到对应的 DOM
setTimeout(() => {
const contentEl = document.getElementById('content');
// 监视目标 DOM 结构变更,会在 useLayoutEffect 回调执行后,useEffect 回调执行前调用
const observer = new MutationObserver(() => {
console.log('content element layout updated');
});
observer.observe(contentEl, {
attributes: true
});
}, 1000);
自定义Hook
通过自定义 Hook,可以将组件逻辑提取到可重用的函数中,在 Hook 特性之前,React 中有两种流行的方式来共享组件之间的状态逻辑:render props和高阶组件,但此类解决方案会导致组件树的层级冗余等问题。而自定义 Hook 的使用可以很好的解决此类问题
创建自定义 Hook
自定义 Hook 是一个函数,其名称以 “use
” 开头,函数内部可以调用其他的 Hook。以下就是实时获取鼠标位置的自定义 Hook 实现:
import { useEffect, useState } from "react"
export const useMouse = () => {
const [position, setPosition] = useState({
x: null,
y: null
});
useEffect(() => {
const moveHandler = (e) => {
setPosition({
x: e.screenX,
y: e.screenY
});
};
document.addEventListener('mousemove', moveHandler);
return () => {
document.removeEventListener('mousemove', moveHandler);
};
}, []);
return position;
}
使用自定义 Hook
自定义 Hook 的使用规则与 Hook 使用规则基本一致,以下是 useMouse
自定义 Hook 的使用过程:
import React from 'react';
import { useMouse } from '../hooks/useMouse';
export default function MouseMove() {
const { x, y } = useMouse();
return (
<>
<p>Move mouse to see changes</p>
<p>x position: {x}</p>
<p>y position: {y}</p>
</>
);
}
每次使用自定义 Hook 时,React 都会执行该函数来获取独立的 state 和执行独立的副作用函数,所有 state 和副作用都是完全隔离的
React Hooks 使用指南的更多相关文章
- React Hooks 完全指南,读React作者博文感悟(2W字精华)
阅读 facebook大佬:Dan Abramov 的文章颇有感悟 大佬 github地址 https://github.com/gaearon 重点总结 useEffect 是同步的 状态是捕获的当 ...
- React Hooks使用避坑指南
函数组件比类组件更加方便实现业务逻辑代码的分离和组件的复用,函数组件也比类组件轻量,没有react hooks之前,函数组件是无法实现LocalState的,这导致有localstate状态的组件无法 ...
- 蒲公英 · JELLY技术周刊 Vol.17: 90 行代码实现 React Hooks
蒲公英 · JELLY技术周刊 Vol.17 React Hooks 相信大家都不陌生,自被设计出以来就备受好评,在很多场景中都有极高的使用率,其中原理更是很多大厂面试中的必考题,很多朋友都能够如数家 ...
- 【独家】React Native 版本升级指南
前言 React Native 作为一款跨端框架,有一个最让人头疼的问题,那就是版本更新.尤其是遇到大版本更新,JavaScript.iOS 和 Android 三端的配置构建文件都有非常大的变动,有 ...
- 蒲公英 · JELLY技术周刊 Vol.21 -- 技术周刊 · React Hooks vs Vue 3 + Composition API
蒲公英 · JELLY技术周刊 Vol.21 选 React 还是 Vue,每个人心中都会有自己的答案,有很多理由去 pick 心水的框架,但是当我们扪心自问,我们真的可以公正的来评价这两者之间的差异 ...
- React重新渲染指南
前言 老早就想写一篇关于React渲染的文章,这两天看到一篇比较不错英文的文章,翻译一下(主要是谷歌翻译,手动狗头),文章底部会附上原文链接. 介绍 React 重新渲染的综合指南.该指南解释了什么是 ...
- 通过 React Hooks 声明式地使用 setInterval
本文由云+社区发表 作者:Dan Abramov 接触 React Hooks 一定时间的你,也许会碰到一个神奇的问题: setInterval 用起来没你想的简单. Ryan Florence 在他 ...
- 初探React Hooks & SSR改造
Hooks React v16.8 发布了 Hooks,其主要是解决跨组件.组件复用的状态管理问题. 在 class 中组件的状态封装在对象中,然后通过单向数据流来组织组件间的状态交互.这种模式下,跨 ...
- React hooks实践
前言 最近要对旧的项目进行重构,统一使用全新的react技术栈.同时,我们也决定尝试使用React hooks来进行开发,但是,由于React hooks崇尚的是使用(也只能使用)function c ...
- 理解 React Hooks
欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由志航发表于云+社区专栏 TL;DR 一句话总结 React Hooks 就是在 react 函数组件中,也可以使用类组件(classe ...
随机推荐
- 供应链安全情报 | cURL最新远程堆溢出漏洞复现与修复建议
漏洞概述 cURL 是一个支持多种网络协议的开源项目,被广泛集成到自动化构建.网络测试.网络数据采集以及其他网络相关的任务中,备受开发者和系统管理员青睐. cURL在2023年10月11日下午紧急发布 ...
- vue如何实现v-model
- Vue 大屏可视化 铺满全屏
https://blog.csdn.net/u011097323/article/details/106728221?utm_medium=distribute.pc_aggpage_search_r ...
- docker目录迁移流程
概述 在安装测试最新版本的HOMER7的过程中,docker作为基础工具碰到一些问题,针对问题进行总结. docker的默认工作目录在/var目录,而在我们的环境中,/var目录空间预留不足,随着do ...
- Spring Boot Actuator 使用和常用配置
转载请注明出处: Spring Boot Actuator是Spring Boot提供的一个非常强大的工具,它可以帮助我们监控和管理我们的Spring Boot应用.Actuator提供了一系列的端点 ...
- Mybatis @Insert插入数据返回自增的主键id
mapper层 @Insert("insert into t_user (username,password,valid,create_time) values (#{username},# ...
- [转帖]mysql 数据库视图迁移
https://www.cnblogs.com/phpyangbo/p/6132821.html 最近做一个项目,为了方便查询,建了好多的视图表,正式上线的时候需要把本地数据库迁移到服务器上. 按照常 ...
- [转帖]Java 容器化的历史坑(史坑) - 资源限制篇
原文:https://blog.mygraphql.com/zh/posts/cloud/containerize/java-containerize/java-containerize-resour ...
- [转帖]Python学习之十七_django的入门
Python学习之十七_django的入门 前言 Python学习了一周, 慢慢总结摸索. 自己还是有多不会的地方. 感慨这些年浪费的时间. 所有的时间都是选择大于努力. 努力最多感动自己. 生活是需 ...
- [转帖]FIO 存储性能压测
一. FIO简介 FIO 是一个多线程IO生成工具,可以生成多种IO模式(随机.顺序.读.写四大类),用来测试磁盘设备的性能.GFIO是FIO的图形监测工具,它提供了图形界面的参数配置,和性能监测图像 ...