I Love ReactJS

React source code interpretation | React.cloneElement analysis

preface

The React.cloneElement method is encountered from time to time, and I read the official website documents many times, but I still have little knowledge of how to use it. Therefore, I go deep into the source code layer to find a calling solution. This article will analyze and summarize the usage of React.cloneElement from the source code layer

version

body

usage

import React from "react";
import "./style.css";

export default function App() {
  const Clone = React.cloneElement(<Temp/>, {key: 123, name: "Zhang San"}, <div>Hello, World 1.</div>, <div>Hello, World 2.</div>)
  return (
    <div>
      {Clone}
    </div>
  );
}

const Temp = (props) => {
  return (
    <div>
        <span>Hello world, {props.name}</span>
        {props.children}
    </div>
  )
};

// Page output
Hello world, Zhang San
Hello, World.1
Hello, World.2

Parameter analysis

element

elements of react

config

object parameter, which can contain the following properties

children

The child element parameter is more appropriate here. Children

parse

  1. declare that props, key, ref, self, source, owner are assigned default values based on 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. in the attributes of config, look for key and ref, and if any, take them out separately and pass them to the new element
if (hasValidRef(config)) {
    // Silently steal the ref from the parent.
    ref = config.ref;
    owner = ReactCurrentOwner.current;
 }
 if (hasValidKey(config)) {
    key = '' + config.key;
 }
  1. traverses the config attribute, filtering the attributes on key, ref, _ _ self, _ _ source and the prototype chain, and assigning the remaining attributes to the props
  2. of the new element

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. traverses the remaining parameters as the children
  2. of the new element

  // 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;
  }

Source code

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);
}
Exit mobile version