本文不考虑各种写法的 CPU 时间开销,只考虑各种写法的开发人员时间开销。
ref 以及其衍生的 forwardRef 是 React 直接提供给组件设计人员的工具,但我个人并不推荐用 ref,也不是很推荐用 forwardRef。 不推荐 ref 的原因:
- 函数式组件没有实例,不能通过 ref 访问其实例。
- 需要新建一个变量,且其初始类型一定为 null,在使用时需要判断。 不太推荐在跨组件场景下用 forwardRef 的原因:
- 函数式组件没有实例。
- 虽然无论如何都要保留一个联系子组件的"指针",但可能有更好的方法,从而不需要将该"指针"存储于单独的位置(而不是组件内部),造成更多的多实例场景下的麻烦。当然,对于子组件联系亲代组件,仍需将该指针保存于单独的位置。
// util.js
export const useBeforeMount = (cb) => useEffect(cb, [])
}
// MessageBox.jsx
export let showMessageBox = () => null
export default () => {
const [visible, setVisible] = useState(false)
const [text, setText] = useState('')
useBeforeMount(() => {
showMessageBox = (text) => {
setVisible(true)
setText(text)
}
})
return <div>...</div>
}
// Customer.jsx
export default () => {
...
showMessageBox('Try linux save your life')
...
}
如果全局有多个实例,则可以考虑使用唯一 id 来识别该方法指向某个实例。在有多个实例的情况下,和 ref 相比,这样做的好处只有阻止对组件内部方法的滥用以及可以在函数式组件中调用其实例的方法。
大同小异,还是需要一个地方保存所需方法。同样可以避免滥用。
// Customer.jsx
export default () => {
const scrollToEndMethodRef = useRef(() => null)
...
return (
<div>
<ScrollView
scrollToEndCallbackProvider={cb = scrollToEndMethodRef.current = cb}
/>
</div>
)
}
// ScrollView.jsx
export default ({scrollToEndCallbackProvider}) => {
const scrollToEnd = ...
useEffect(() => {
scrollToEndCallbackProvider(scrollToEnd)
}, [scrollToEndCallbackProvider])
return ...
}
不太推荐,如果是简单的 true/false 还好,如果是数值/字符串的话,像是从 "网络错误" 变成 "网络错误" 这种更新了但没完全更新的情况就糟糕了。并且这里的 true/false 也是没有意义的,只是利用了值的更新。
// Customer.jsx
export default () => {
const [showFilterFlipFlop, setShowFilterFlipFlop] = useState(false)
...
return (
<div>
<Whatever
showFilterFlipFlop={showFilterFlipFlop}
/>
</div>
)
}
// Whatever.jsx
export default ({showFilterFlipFlop}) => {
const showFilter = ...
useEffect(() => {
showFilter()
}, [showFilterFlipFlop])
return ...
}