React源码解读 | React.cloneElement分析

4,236

前言

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

解析

  1. 声明 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;
  1. 在 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;
 }
  1. 遍历 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];
        }
   }
}
  1. 遍历剩余的参数,作为新元素的 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);
}