ref 回调:
当需要设置 ref 时,React 将传入 DOM 节点来调用你的 ref 回调,并在需要清除它时传入 null
这样就可以维护自己的数组或者 Map
如:
import { useRef } from 'react'
export default function CatFriends() {
const itemsRef = useRef(null)
function scrollToId(itemId) {
const map = getMap()
const node = map.get(itemId)
node.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'center',
})
}
function getMap() {
if (!itemsRef.current) {
// 首次运行时初始化 Map。
itemsRef.current = new Map()
}
return itemsRef.current
}
return (
<>
<nav>
<button onClick={() => scrollToId(0)}>Tom</button>
<button onClick={() => scrollToId(5)}>Maru</button>
<button onClick={() => scrollToId(9)}>Jellylorum</button>
</nav>
<div>
<ul>
{catList.map((cat) => (
<li
key={cat.id}
ref={(node) => {
const map = getMap()
if (node) {
map.set(cat.id, node)
} else {
map.delete(cat.id)
}
}}
>
<img src={cat.imageUrl} alt={'Cat #' + cat.id} />
</li>
))}
</ul>
</div>
</>
)
}
一个组件可以指定将它的 ref “转发”给一个子组件。下面是 MyInput 如何使用 forwardRef API
const MyInput = forwardRef((props, ref) => {
return <input {...props} ref={ref} />
})
当然,子组件必须是 DOM,函数组件使用 ref 会获取 null
import { useState, useRef } from 'react'
export default function TodoList() {
const listRef = useRef(null)
const [text, setText] = useState('')
const [todos, setTodos] = useState(initialTodos)
function handleAdd() {
const newTodo = { id: nextId++, text: text }
setText('')
setTodos([...todos, newTodo])
listRef.current.lastChild.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
})
}
return (
<>
<button onClick={handleAdd}>添加</button>
<input value={text} onChange={(e) => setText(e.target.value)} />
<ul ref={listRef}>
{todos.map((todo) => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
</>
)
}
添加一个新的待办事项后,总是滚动到最后一个添加之前的待办事项
因为 setState 调用后立刻调用了 DOM 滚动,此时 DOM 还是旧 DOM,新 state 尚未来得及渲染并绘制新 DOM
所以需要强制 React 同步更新(“刷新”)DOM
从 react-dom 导入 flushSync 并将 state 更新包裹 到 flushSync 调用中:
flushSync(() => {
setTodos([...todos, newTodo])
})
listRef.current.lastChild.scrollIntoView()
Effect 允许你指定由渲染本身,而不是特定事件引起的副作用
Effect 是 react 绘制 dom 后执行
如 setTimeout 需要返回 clearTimeout
如 fetch 可以在 then 中处理
import { useState, useEffect } from 'react'
import { fetchBio } from './api.js'
export default function Page() {
const [person, setPerson] = useState('Alice')
const [bio, setBio] = useState(null)
useEffect(() => {
let ignore = false
setBio(null)
fetchBio(person).then((result) => {
if (!ignore) {
setBio(result)
}
})
return () => {
ignore = true
}
}, [person])
return (
<>
<select
value={person}
onChange={(e) => {
setPerson(e.target.value)
}}
>
<option value="Alice">Alice</option>
<option value="Bob">Bob</option>
<option value="Taylor">Taylor</option>
</select>
<hr />
<p>
<i>{bio ?? '加载中……'}</i>
</p>
</>
)
}