1、React.forwardRef的應用場景
ref 的做用是獲取實例,多是 DOM 實例,也多是 ClassComponent 的實例。javascript
但會碰到如下問題:
① 若是目標組件是一個 FunctionComponent 的話,是沒有實例的(PureComponent),此時用 ref 去傳遞會報錯:html
② 若是你是一個庫的開發者的話,使用該庫的人是不知道庫的組件類別的,那麼當庫組件類別是 FunctionComponent 的時候,使用者想用 ref 獲取庫組件,怎麼辦?java
③ redux 中的 connect 方法將組件包裝成高階組件(HOC),那麼我如何經過 ref 去獲取包裝前的組件實例?react
④ props 不能傳遞 refweb
React 官方也表述了 ref 的使用條件:redux
React.forwardRef
存在的意義就是爲了解決以上問題。api
關於React.forwardRef
的使用,請參考:bash
zh-hans.reactjs.org/docs/react-…app
接下來咱們講下React.forwardRef
的源碼ide
2、React.forwardRef
做用:
建立一個 React 組件,該組件可以將其接收的 ref 屬性轉發到內部的一個組件中
源碼:
export default function forwardRef<Props, ElementType: React$ElementType>(
render: (props: Props, ref: React$Ref<ElementType>) => React$Node,
) {
//刪除了 dev 代碼
return {
//被forwardRef包裹後,組件內部的$$typeof是REACT_FORWARD_REF_TYPE
$$typeof: REACT_FORWARD_REF_TYPE,
//render即包裝的FunctionComponent,ClassComponent是不用forwardRef的
render,
};
}
複製代碼
解析:
(1) 返回的是一個對象,即下面的變量 Child:
const Child = React.forwardRef((props, ref) => (
<button ref={ref}>
{props.children}
</button>
));
console.log(Child,'Child29')
複製代碼
(2) 返回的對象裏面的$$typeof
,並不說明Child
組件類型是REACT_FORWARD_REF_TYPE
我以前寫的 React源碼解析之React.createElement()和ReactElement() 中有提到ReactElement
的type
爲REACT_ELEMENT_TYPE:
const ReactElement = function(type, key, ref, self, source, owner, props) {
const element = {
// This tag allows us to uniquely identify this as a React Element
//標識element的類型
//由於jsx都是經過createElement建立的,因此ReactElement的類型固定:爲REACT_ELEMENT_TYPE
//重要!由於react最終渲染到DOM上時,須要判斷$$typeof===REACT_ELEMENT_TYPE
$$typeof: REACT_ELEMENT_TYPE,
// Built-in properties that belong on the element
//設置元素的內置屬性
type: type,
};
}
複製代碼
注意:
① 變量Child
是對象:
{
$$typeof: REACT_FORWARD_REF_TYPE,
render,
};
複製代碼
Child
組件是 ReactElement 對象,二者不同:
const element = {
$$typeof: REACT_ELEMENT_TYPE,
type: type,
};
複製代碼
當把Child
轉變爲 ReactElement 的時候,Child
對象做爲Child
組件的type
屬性:
const element = {
$$typeof: REACT_ELEMENT_TYPE,
//注意!!!
type: {
$$typeof: REACT_FORWARD_REF_TYPE,
render,
},
};
複製代碼
不要認爲被forwardRef
包裹後,React 組件的$$typeof
的值會改變成REACT_FORWARD_REF_TYPE
!
② 只有 FunctionComponent 才用獲得forwardRef
,ClassComponent 不須要
3、updateForwardRef
做用:
更新ref
指向及被React.forwardRef
包裹的FunctionComponent
源碼:
//更新被React.forwardRef包裹的 FunctionComponent
function updateForwardRef(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
nextProps: any,
renderExpirationTime: ExpirationTime,
) {
// TODO: current can be non-null here even if the component
// hasn't yet mounted. This happens after the first render suspends.
// We'll need to figure out if this is fine or can cause issues.
//刪除了 dev 代碼
//Component:{
// $$typeof: REACT_FORWARD_REF_TYPE,
// render,
// }
//FunctionComponent
const render = Component.render;
// 開發層面上不容許FunctionComponent,但你打印 props 的話是有的,
// 由於是 React 只容許內部經過 props 傳進來 ref
const ref = workInProgress.ref;
// The rest is a fork of updateFunctionComponent
let nextChildren;
//context 相關的可跳過
prepareToReadContext(workInProgress, renderExpirationTime);
prepareToReadEventComponents(workInProgress);
if (__DEV__) {
//刪除了 dev 代碼
} else {
//渲染的過程當中,對裏面用到的 hook函數作一些操做
//關於renderWithHooks的講解,請看:https://www.jianshu.com/p/959498695e83
//注意:在updateFunctionComponent()中傳的參數不是 ref,
//而是 context:nextChildren = renderWithHooks(
// current,
// workInProgress,
// Component,
// nextProps,
// 傳的是 context 而不是 ref
// context,
// renderExpirationTime,
// );
nextChildren = renderWithHooks(
current,
workInProgress,
render,
nextProps,
ref,
renderExpirationTime,
);
//renderWithHooks 內部經過let children = Component(props, refOrContext)來更新 ref 或 context
}
//若是 props 相同,而且 ref 也相同的話,就不須要更新
if (current !== null && !didReceiveUpdate) {
//跳過hooks更新
//關於bailoutHooks的講解,請看:https://www.jianshu.com/p/959498695e83
bailoutHooks(current, workInProgress, renderExpirationTime);
//跳過該節點及全部子節點的更新
//關於bailoutOnAlreadyFinishedWork的講解,請看:https://www.jianshu.com/p/06b18db8b5d4
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
}
// React DevTools reads this flag.
workInProgress.effectTag |= PerformedWork;
//將 ReactElement 變成 fiber對象,並更新,生成對應 DOM 的實例,並掛載到真正的 DOM 節點上
//關於reconcileChildren的講解,請看:https://www.jianshu.com/p/959498695e83
reconcileChildren(
current,
workInProgress,
nextChildren,
renderExpirationTime,
);
return workInProgress.child;
}
複製代碼
解析:
(1) 邏輯比較簡單,裏面大部分函數在以前的文章裏已解析過:
① 關於 renderWithHooks
、bailoutHooks
、reconcileChildren
的講解,請看:
React源碼解析之FunctionComponent(上)
② 關於 bailoutOnAlreadyFinishedWork
的講解,請看:
React源碼解析之workLoop
(2) ref 的更新是在renderWithHooks
中:
let children = Component(props, refOrContext);
複製代碼
這裏的Component
是2、React.forwardRef
中 Child 對象的render
屬性,也就是執行渲染FunctionComponent
的方法
refOrContext
在這裏是workInProgress.ref
也就是說Component(props, refOrContext)
的參數即React.forwardRef
中的參數(props, ref)
:
React.forwardRef((props, ref) => (
xxx
));
複製代碼
(完)