前言
React.cloneElement 方法时不时遇到,多次翻阅官网文档,对其使用方式仍是一知半解,因此,深入源码层,寻找调用方案。此文将从源码层分析总结React.cloneElement 的使用方法
版本
- react源码版本:16.6.1
正文
用法
- 以 element 元素为样板克隆并返回新的React元素,可以通过config参数修改 props 、 key 、 ref ,可以通过 children 修改子元素
- React.cloneElement中可以传入任意个参数,从第三个参数开始都将作为新React元素的子元素
- 示例
import React from "react";
import "./style.css";
export default function App() {
const Clone = React.cloneElement(<Temp/>, {key: 123, name: "张三"}, <div>你好世界1</div>, <div>你好世界2</div>)
return (
<div>
{Clone}
</div>
);
}
const Temp = (props) => {
return (
<div>
<span>你好世界,{props.name}</span>
{props.children}
</div>
)
};
// 页面输出
你好世界,张三
你好世界1
你好世界2
参数分析
element
react的元素
config
对象参数,可以包含以下属性
- ref:非必填,替换原element中的 ref
- key:非必填,替换原element中的 key
- 其他属性:全部作为新element的 props
children
子元素参数,此处更合适的应该是 ...children
解析
- 声明 props 、 key 、 ref 、 self 、 source 、 owner 根据 element 赋默认值
const props = Object.assign({}, element.props);
// Reserved names are extracted
let key = element.key;
let ref = element.ref;
// Self is preserved since the owner is preserved.
const self = element._self;
// Source is preserved since cloneElement is unlikely to be targeted by a
// transpiler, and the original source is probably a better indicator of the
// true owner.
const source = element._source;
// Owner will be preserved, unless ref is overridden
let owner = element._owner;
- 在 config 的属性中,查找 key 和 ref ,如果有将其单独取出传给新的元素
if (hasValidRef(config)) {
// Silently steal the ref from the parent.
ref = config.ref;
owner = ReactCurrentOwner.current;
}
if (hasValidKey(config)) {
key = '' + config.key;
}
- 遍历 config 属性,过滤 key 、 ref 、 __self 、 __source 以及原型链上的属性,剩余的属性赋值给新元素的 props
for (propName in config) {
if (
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
if (config[propName] === undefined && defaultProps !== undefined) {
// Resolve default props
props[propName] = defaultProps[propName];
} else {
props[propName] = config[propName];
}
}
}
- 遍历剩余的参数,作为新元素的 children
// Children can be more than one argument, and those are transferred onto
// the newly allocated props object.
const childrenLength = arguments.length - 2;
if (childrenLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
const childArray = Array(childrenLength);
for (let i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
props.children = childArray;
}
流程图
源码
import invariant from 'shared/invariant';
import ReactCurrentOwner from './ReactCurrentOwner';
const hasOwnProperty = Object.prototype.hasOwnProperty;
const RESERVED_PROPS = {
key: true,
ref: true,
__self: true,
__source: true,
};
/**
* Clone and return a new ReactElement using element as the starting point.
* See https://reactjs.org/docs/react-api.html#cloneelement
*/
export function cloneElement(element, config, children) {
invariant(
!(element === null || element === undefined),
'React.cloneElement(...): The argument must be a React element, but you passed %s.',
element,
);
let propName;
// Original props are copied
const props = Object.assign({}, element.props);
// Reserved names are extracted
let key = element.key;
let ref = element.ref;
// Self is preserved since the owner is preserved.
const self = element._self;
// Source is preserved since cloneElement is unlikely to be targeted by a
// transpiler, and the original source is probably a better indicator of the
// true owner.
const source = element._source;
// Owner will be preserved, unless ref is overridden
let owner = element._owner;
if (config != null) {
if (hasValidRef(config)) {
// Silently steal the ref from the parent.
ref = config.ref;
owner = ReactCurrentOwner.current;
}
if (hasValidKey(config)) {
key = '' + config.key;
}
// Remaining properties override existing props
let defaultProps;
if (element.type && element.type.defaultProps) {
defaultProps = element.type.defaultProps;
}
for (propName in config) {
if (
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
if (config[propName] === undefined && defaultProps !== undefined) {
// Resolve default props
props[propName] = defaultProps[propName];
} else {
props[propName] = config[propName];
}
}
}
}
// Children can be more than one argument, and those are transferred onto
// the newly allocated props object.
const childrenLength = arguments.length - 2;
if (childrenLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
const childArray = Array(childrenLength);
for (let i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
props.children = childArray;
}
return ReactElement(element.type, key, ref, self, source, owner, props);
}