使用React.cloneElement()函数来克隆元素

5,805 阅读8分钟

简介

什么是React.cloneElement()

[React.cloneElement()](https://reactjs.org/docs/react-api.html#cloneelement)是React顶层API的一部分,用于操作元素。它克隆并返回一个新的元素,使用其第一个参数作为起点。这个参数可以是一个React元素或一个渲染React元素的组件。

新的元素将有以下特点。

  • 原有元素的道具与新的道具浅浅地合并在一起
  • 新的子元素取代现有的子元素
  • 原元素中的keyref 被保留。

当你想添加或修改一个父组件的子元素的道具时,React.cloneElement() ,同时避免不必要的重复代码。

语法和使用React.cloneElement()

在我们看一些例子之前,让我们先看看React.cloneElement() 的语法。语法在下一个代码块中给出,后面是一些术语的定义。

React.cloneElement(element, [props], [...children])

  • element: 将被克隆的元素
  • [props] :除了原元素的道具外,将被添加到克隆的元素中的道具
  • [...children]: 克隆对象的子女。注意,现有对象的子元素不会被复制。

你可以在父组件的定义中使用React.cloneElement() ,以执行下列过程。

  • 修改子元素属性
  • 添加到子组件属性
  • 扩展子组件的功能

本文通过以下例子深入探讨这三种操作的每一种。

修改子元素属性

当你修改子元素的属性时,这意味着你通过传递给父元素来改变子元素的属性

没有比通过真实的代码例子更好的解释方式了。我们将用下面的例子来说明这个概念。

  1. 重复的字符
  2. 花式子按钮
  3. 一次性修改单选按钮的属性
  4. 克隆React元素作为一个属性

在我们继续之前,请注意在这些例子中,所修改的属性是浅层合并的--keyref 是保持的。还没有创建新的子节点,尽管我们会在本文的最后一个例子中看一下。

1.重复的字符

在下一个代码块中,RepeatCharacters 是一个父组件,CreateTextWithProps 是一个子组件。

CreateTextWithProps 有一个名为ASCIIChar 的属性,其值是任何有效的 ASCII 字符。RepeatCharacters 将使用React.cloneElement() 在克隆的元素中重复这个字符,其次数在times 属性中指定。

import React from "react";
​
const CreateTextWithProps = ({ text, ASCIIChar, ...props }) => {
 return (
   <span {...props}>
    {text}{ASCIIChar}
   </span>
)
};
​
const RepeatCharacters = ({ times, children }) => {
 return React.cloneElement(children, {
   // This will override the original ASCIIChar in the text.
   ASCIIChar: children.props.ASCIIChar.repeat(times),
})
};
​
function App() {
 return (
   <div>
     <RepeatCharacters times={3}>
       <CreateTextWithProps
         text="Habdul Hazeez"
         ASCIIChar='.'
         />
     </RepeatCharacters>
   </div>
)
}
​
export default App

下面是在网络浏览器中看到的结果。

Result of the repeating characters as seen in the web browser

2.花哨的儿童按钮

这里没有什么花哨的东西--这是一些代码,显示一个按钮,上面有 "Fancy button "的文字。这证明你可以定制这些组件以满足你的需要。

ButtonContainer 组件使用React.cloneElement() 来修改由Button 组件渲染的元素的外观。

在这种情况下,你提供null 作为React.cloneElement() 的第三个参数,因为作为提醒,你并没有创建任何新的子元素。

import React from "react";
​
const ButtonContainer = (props) => {
 let newProp = {
   backgroundColor: "#1560bd",
   textColor: '#ffffff',
   border: '1px solid #cccccc',
   padding: '0.2em',
}
​
 return (
   <div>
    {React.Children.map(props.children, child => {
       return React.cloneElement(child, {newProp}, null)
    })}
   </div>
)
};
​
const Button = (props) => {
 return <button
   style={{
     color: props.newProp.textColor,
     border: props.newProp.border,
     padding: props.newProp.padding,
     backgroundColor: props.newProp.backgroundColor
  }}>Fancy Button</button>
}
​
function App() {
 return (
   <ButtonContainer>
     <Button />
   </ButtonContainer>
)
}
​
export default App

当你在网络浏览器中查看结果时,你会得到与下面的图片类似的东西。

The fancy button in the web browser

3.修改单选按钮的属性

在HTML中,单选按钮是分组的。你只能从提供的选项中选择一个,而且它们似乎总是有一个name 属性附加在上面。

利用到目前为止你所获得的知识,你可以通过一个父组件动态地将这个name 属性添加到多个子组件中。

在这种情况下,子组件应该是单选按钮或者是渲染单选按钮的组件。

import React from "react";
​
const RadioGroup = (props) => {
 const RenderChildren = () => (
   React.Children.map(props.children, child => {
     return React.cloneElement(child, {
       name: props.name,
    })
  })
)
​
 return (
   <div>
    {<RenderChildren />}
   </div>
)
}
​
const RadioButton = (props) => {
 return (
   <label>
     <input type="radio" value={props.value} name={props.name} />
    {props.children}
   </label>
)
}
​
function App() {
 return (
 <RadioGroup name="numbers">
   <RadioButton value="first">First</RadioButton>
   <RadioButton value="second">Second</RadioButton>
   <RadioButton value="third">Third</RadioButton>
 </RadioGroup>
)
}
​
export default App

你可以使用你的浏览器的检查元素功能来确认这一点。

Confirming the radio buttons in your browser

4.克隆另一个React元素作为一个道具

你很可能会发现自己处于这样一种情况:你必须为不同的网页创建一个具有不同文字的网站标题。

在你的React武器库中,有多种选择,其中可能包括将标题文本存储在一个变量中,将这个变量作为一个道具传递给一个组件,并渲染文本。然后你可能会在每个需要这个标题文本的组件中这样做。

正如你可能猜到的那样,这造成了不必要的代码重复,而作为软件开发者,我们应该始终主张保持事情的简单。

相反,你可以使用React.cloneElement 来更容易地实现同样的目标。创建三个可重复使用的组件,即。

  • Header
  • DefaultHeader
  • BigHeader

Header 组件将接收一个组件作为道具。这个组件最终将渲染标题文本。为了安全起见,DefaultHeader 将是传递给Header 的默认组件。

DefaultHeader 将渲染默认文本。当调用Header ,没有道具时,这个默认文本就会被呈现出来。

同时,BigHeader 组件将有一个message 道具,其值是你选择的标题文本。每次你把BigHeader 传递给Header ,你都可以在BigHeader 渲染之前修改这个message 的道具值。

所有这些在下一个代码块中都有说明。

import React from "react";
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
​
const DefaultHeader = (color) => {
 return (
   <div style={{ color: "#1560bd" }}>
     <p>Website of Habdul Hazeez</p>
   </div>
)
}
​
const defaultMessage = 'Website of Habdul Hazeez';
​
const BigHeader = ({ color, message = defaultMessage }) => {
 return (
   <div style={{ color, fontSize: '2em' }}>
    {message}
   </div>
)
}
​
const Header = ({ hero = <DefaultHeader />}) => {
 return (
   <div>
    {React.cloneElement(hero, { color: "#1560bd"})}
   </div>
)
}
​
const HomePage = () => {
 return (

<Header hero={<BigHeader message="This is the home page" />} />
)
}
​
const AboutMe = () => {
 return (
   <Header hero={<BigHeader message="Information about me" />} />
)
}
​
const ContactPage = () => {
 return (
   <Header hero={<BigHeader message="This contains my contact information." />} />
)
}
​
function App() {
 return (
   <React.Fragment>
     <Router>
       <nav>
         <ul>
           <li>
             <Link to="/">Home</Link>
           </li>
           <li>
             <Link to="/contact-page">Contact</Link>
           </li>
           <li>
             <Link to="about-me">About</Link>
           </li>
         </ul>
       </nav>
       
       <Route exact path="/"><HomePage /></Route>
        <Route path="/contact-page"><ContactPage /></Route>
        <Route path="/about-me"><AboutMe /></Route>
      </Router>
    </React.Fragment>
)
}
​
export default App

添加到子属性

当你添加到一个子属性时,这意味着你已经通过父组件将新的东西插入到子属性中。

本节讨论的以下例子将解释如何通过React.cloneElement() 来添加道具。

注意,正如我在上一节中所说的,新的道具被合并进来,keyref 被维护,新的子属性不会被创建(至少目前是这样)。

粗体字

当你看到这一节的标题时,不要气馁,认为:"这很容易"。我知道这很 "容易"--从表面上看。这里的课程是使用React.cloneElement() ,向子元素添加一个属性。

为了告诉你这是如何工作的,我们将重新审视本文的第一个例子,关于重复字符。不过,你不会重复任何字符--相反,你将定义一个自定义的CSS样式,并使用React.cloneElement() 函数添加到子元素中。

这在下一个代码块中有所说明。

import React from "react";

const CreateTextWithProps = ({ text, ...props }) => {
  return (
    <span {...props}>
      {text}
    </span>
  )
};

const BoldText = ({ children }) => {
  return React.cloneElement(children, {
    style: {
      fontWeight: 'bold'
    },
  })
};

function App() {
  return (
    <div>
      <BoldText>
        <CreateTextWithProps
          text="Habdul Hazeez"
        />
      </BoldText>
    </div>
  )
}

export default App;

将道具传递给一个通过以下方式接收的元素React.cloneElement()

这类似于你在上面的 "克隆另一个React元素作为道具 "一节中所做的,但在这里,你将使用React.cloneElement() 克隆一个道具。这个道具必须是一个有效的React元素,比如<h1><button>

当你克隆这个道具时,你可以传递额外的属性,如CSS样式或事件处理器。

在下一个代码块中,你将传递一个handleClick 函数给这个道具,它将被AlertOnClick 。因此,每次元素被点击时,handleClick 内的代码就会被执行。

import React from "react";
​
const AlertOnClick = (props) => {
 
 function handleClick () {
   alert("Hello World")
}
​
 const Trigger = props.trigger;
​
 return (
   <div>
    {React.cloneElement(Trigger, {
       onClick: handleClick
    })}
   </div>
)
}
​
function App() {
 
 return (
   <div>
    {
       <AlertOnClick trigger={<button>Click me</button>} />
    }
   </div>
)
}
​
export default App

扩展子组件的功能

到目前为止,你已经修改和添加了子组件的道具。这个例子展示了克隆元素的最后一个特征,因为我们终于创建了一个新的子元素!这个新的子元素被传递给了第三个子元素。这个新的子元素被作为第三个参数传递给React.cloneElement()

点击时的警报

在这个例子中,我们将扩展子元素的功能

在下一个代码块中,通过React.cloneElement()Button 组件创建的新按钮元素有两个额外的转折:一个onClick 事件处理程序和新文本。

import React from "react";
const Button = () => {
 return (
   <button type="button" style={{ padding: "10px" }}>
     This is a styled button
   </button>
)
}
​
function App() {
 return (
   <section>
    {
       React.cloneElement(
         Button(), // component to overwrite
        {
           
           onClick: () => { // additional props
             alert("You are making progress!!!")
          }
        },
         <>
           Styled button with onClick
         </>
      )
    }
   </section>
)
}
​
export default App

当这段代码被执行时,按钮将呈现出通过React.cloneElement() 函数添加的文本,其内容为 "Styled button with onClick",如下图所示。

The styled button

总结

这篇文章用七个不同的例子解释了如何使用React.cloneElement() 函数。其中四个例子解释了如何修改子组件的道具;另外两个例子详细说明了如何向子组件添加道具;最后,我们的最后一个例子展示了如何扩展子组件的功能。

希望你在本文中学到的东西可以为你的React应用增添一些创意的色彩

The postUsing the React.cloneElement() function to clone elements appearedfirst on LogRocketBlog.