forwardRef
forwardRef 是什么?
是 react 官方提供的一个高阶函数,方便将内部对应的 ref 转给外部,只能用于函数组件。
场景有哪些?
个人觉得更多是在公共组件中使用,它能将 ref 转发到内部组件某个节点上,如下场景
import { useRef, useState, useImperativeHandle } from 'react'
const NO_UPDATE_TEXT = 'you not update anything!';
const Main = () => {
const [text, setText] = useState('Main');
useImperativeHandle(ref, () => ({
update: (newText) => {
setText(newText || NO_UPDATE_TEXT);
}
}));
return <div>{text}</div>
}
const Tip = () => {
return <div>tip</div>
}
const Demo = forwardRef((props, ref) => {
return (
<>
<Main ref={ref} />
<Tip />
</>
);
})
const App = () => {
const demoRef = useRef();
const handleUpdate = () => {
demoRef.current.update('Click update');
}
return (
<div>
<Demo ref={demoRef} />
<button onClick={handleUpdate}>update</button>
</div>
);
}
- 比如上面的 Demo 组件是个显示组件,暴露的 ref 更多是想让调用方去控制 main 相关的,而不是tip,表明组件开发者,想减少外部干预 tip,总结一句话 “我转发 ref 是想让你获得更多的内部控制,避免你乱调用,引发不可控问题。”
- 相比于 Main 组件,Demo 组件可能更多是组合组件,所以可提供的调用的方法不多,那么 Demo 组件更多是保留 Main 组件的能力,通过 forwordref 转发出去。
实现原理 - 源码解析
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {REACT_FORWARD_REF_TYPE, REACT_MEMO_TYPE} from 'shared/ReactSymbols';
export function forwardRef<Props, ElementType: React$ElementType>(
render: (props: Props, ref: React$Ref<ElementType>) => React$Node,
) {
// 以下是开发模式下,做的一些错误提示,指导用户更好的使用函数方法
if (__DEV__) {
// 判断memo错误组合用法,会输出错误提示
if (render != null && render.$$typeof === REACT_MEMO_TYPE) {
console.error(
'forwardRef requires a render function but received a `memo` ' +
'component. Instead of forwardRef(memo(...)), use ' +
'memo(forwardRef(...)).',
);
// 如果函数包裹对不是函数,那么属于使用错误,提示异常
} else if (typeof render !== 'function') {
console.error(
'forwardRef requires a render function but was given %s.',
render === null ? 'null' : typeof render,
);
} else {
// 判断函数入参数,只接受两个参数 porps 和 ref
if (render.length !== 0 && render.length !== 2) {
console.error(
'forwardRef render functions accept exactly two parameters: props and ref. %s',
render.length === 1
? 'Did you forget to use the ref parameter?'
: 'Any additional parameter will be undefined.',
);
}
}
// 利用defaultProps和propTypes判断传递的是不是函数组件,因为函数组件,不具有defaultProps和propTypes属性。
if (render != null) {
if (render.defaultProps != null || render.propTypes != null) {
console.error(
'forwardRef render functions do not support propTypes or defaultProps. ' +
'Did you accidentally pass a React component?',
);
}
}
}
const elementType = {
$$typeof: REACT_FORWARD_REF_TYPE,
render,
};
if (__DEV__) {
let ownName;
Object.defineProperty(elementType, 'displayName', {
enumerable: false,
configurable: true,
get: function() {
return ownName;
},
set: function(name) {
ownName = name;
// 下面官方注释,主要讲了通过匿名函数,继承组件的名称,让组件能在被函数包裹的情况下,在组件堆栈中显示,方便调试。
// The inner component shouldn't inherit this display name in most cases,
// because the component may be used elsewhere.
// But it's nice for anonymous functions to inherit the name,
// so that our component-stack generation logic will display their frames.
// An anonymous function generally suggests a pattern like:
// React.forwardRef((props, ref) => {...});
// This kind of inner function is not used elsewhere so the side effect is okay.
if (!render.name && !render.displayName) {
render.displayName = name;
}
},
});
}
return elementType;
}
useimperativehandle
useimperativehandle 是什么?
他也是 react 官方提供的工具方法,不过是以 hooks 形式。在 react 的组合式思想里,其实涉及到父子组件的控制问题。我们可以思考一下,如果以业务角度出发,更多是把子组件的相关业务控制,保留在父组件中,产生业务逻辑组件, 但是做业务开发,有趣的就是什么场景都会发生。那么我想抽离一个公共的业务组件,那它就带有两种特性“公共模块化”、“业务依赖性”。它看似独立,单又不完全独立。useimperativehandle 给到我们,更多在公共模块化角度去设计,提供一些自定义方法,让公共业务组件的某一个业务功能,单一向外暴露,提高组件的易用性。
上面提到了它在业务性公共组件上的场景,从公共模块化的能力提升,可以知道它在公共组件设计中非常普遍使用了。对于公共组件来说,内部会有很多已经设计好的一体化功能,调用方,可以直接使用,不用太多使用成本。但是业务场景是有趣的,只有你想不到的场景,对于公共组件,也要有基础能力的提供,useimperativehandle 更多的是由组件内部向外部暴露基础方法,便于外部能自行拓展。不过我个人觉得暴露的基础能力,需要严格控制,否则在后期维护上,会增加组件的迭代成本。
使用场景思考
目前我主要是在项目中担任业务研发角色,我其实发现大家更愿意将业务组装在一起,因为这样完全不用设计,唯一缺点就是冗余,如果我们稍微思考,其实大部分单一逻辑,可以封装到子组件中,将整合冗余的业务拆分。但是大家并不会去这么做,因为封装行为,本身就需要消耗精力。如果你还在抱怨业务中没成长,每天只是单纯写需求写页面,其实你需要给自己一点挑战。最近一段时间,自己在做大模块重构,其实做更多的设计,并不会太影响模块研发时间。我们觉得会额外耗时,只是因为我们害怕对未知的设计,带来的恐惧,面对恐惧最好对办法,就是勇往直前,干它。