We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
本文作者:IMWeb howenhuo 原文出处:IMWeb社区 未经同意,禁止转载
原文链接:How To Master Advanced React Design Patterns: Compound Components
为了庆祝 React 16.3 的正式发布,我决定分享我最近使用的一些技术,这些技术彻底改变了我创建 React 组件的方法。因此,我能够设计出完全可重用的组件,并且可以在许多不同的环境中灵活地使用这些组件。
单击此处查看本系列的第2部分:Context API
上面的 sandbox 是一个简洁的 Stepper 组件的初始代码,我将使用它来展示其中的一些技术。 就目前而言,这个组件完全正常工作,并且完全按照设计目的进行,但它缺乏灵活性。
sandbox
Stepper
import React, { Component } from 'react'; import Stepper from "./Stepper" class App extends Component { render() { return ( <Stepper stage={1}/> ); } } export default App;
如上可视,Stepper 组件的灵活性在 stage 属性处终止;我们只能修改 stage 的值来确定 Stepper 组件进行到哪一步。
stage
就目前而言,我要实现这些变化的唯一方法是完全重写组件,以相同的方式重写一个类似的组件。 但是,如果将来又要进行其他更改,那该组件又一次需要重写。 因此,让我们尝试不同的方法来重写组件,使其具有灵活性和可重用性,以应变将来任何的配置。
在本系列的第一部分中,我们将探讨一种名为“复合组件”的设计模式
首先,让我们来看看 Stepper 组件。
class Stepper extends Component { state = { stage: this.props.stage } static defaultProps = { stage: 1 } handleClick = () => { this.setState({ stage: this.state.stage + 1 }) } render() { const { stage } = this.state; return ( <div style={styles.container}> <Progress stage={stage}/> <Steps handleClick={this.handleClick} stage={stage}/> </div> ); } } export default Stepper;
Stepper 组件有一个存储当前 stage 的状态对象,一个增加 stage 属性值的方法,以及一个 render 方法,它返回包含2个子组件的div。
render
目前,我们明确地将 Progress 和 Steps 组件直接放在 Stepper 组件中。 为了减少这种静态写法,我们可以使用 props 对象动态插入子组件。
Progress
Steps
props
在 Stepper.js 文件中使用 props.children 对象替换 Progress 和 Steps 组件,并将它们放在 App.js中的 Stepper 组件内。
Stepper.js
props.children
App.js
只需这简单的改变就给我们带来很大的收益。现在我们可以选择组件树中的哪个组件先渲染; 我们可以选择进度块是在左侧还是右侧。
但这种方法有一个问题: Progress 和 Steps 组件不能再访问 stage 和 handleClick 属性了。 为了让每个子组件获取它们需要的属性,我们需要手动遍历每个子组件并向其注入这些属性。 我们可以使用 react API 提供的一些辅助方法来实现。 两个方法是: Children.map() 和 cloneElement()。
handleClick
Children.map()
cloneElement()
const children = React.Children.map(this.props.children, child => { return React.cloneElement(child, {stage, handleClick: this.handleClick}) })
Children.map() 类似于 Array.map() 方法。但请务必使用Children.map(),因为 children.props 具有不透明的数据结构,使得 Array.map() 方法不适合此用例。
Array.map()
children.props
cloneElement 如名称一样,它克隆这些子组件并可以注入额外的属性,最后返回新的组件。
cloneElement
// Render method of Stepper.js const { stage } = this.state; const children = React.Children.map(this.props.children, child => { return React.cloneElement(child, {stage, handleClick: this.handleClick}) }); return ( <div style={styles.container}> {children} </div> );
现在我们可以将 Progress 和 Stage 作为子项添加到 Stepper 组件中,运行效果不变。但此时我们可以决定每个组件的位置,甚至可以在左右两侧同时设置进度块。
Stage
import React, { Component } from 'react'; import Stepper from "./Stepper" import Progress from './components/Progress'; import Steps from './components/Steps'; class App extends Component { render() { return ( <div> <Stepper stage={1}> <Progress /> <Steps /> </Stepper> </div> ); } } export default App;
另外一种能够提高可读性和易用性的技术就是使用类的静态属性。它允许我们直接在类上调用方法。
这是什么意思? 让我们来看看…。
首先,我们在 Stepper 组件中创建两个静态方法,并将 Progress 和 Steps 组件赋值给它们:
static Progress = Progress static Steps = Steps
那么现在,我们不需要再到处引入 Progress 和 Steps 组件,而是直接从 Stepper 组件中引用它们:
import React, { Component } from 'react'; import Stepper from "./Stepper" class App extends Component { render() { return ( <Stepper stage={1}> <Stepper.Progress /> <Stepper.Steps /> </Stepper> ); } } export default App;
到目前为止,我们已经创建了一个简单可读且灵活的API。那么是不是可以对 Progress 组件使用相同的技术呢? 让我们来看看......
export default class Progress extends Component { render(){ const {stage} = this.props return( <div style={styles.progressContainer}> <Stage stage={stage} num={1} /> <Stage stage={stage} num={2} /> <Stage stage={stage} num={3} /> <Stage stage={stage} num={4} /> </div> ) } }
您也许已经注意到 Progress 组件与之前的 Stepper 组件存在同样的问题。所以我们用 props.children 对象来替换这 4 个 Stage 组件并遍历子项添加所需的属性,然后在 Stepper 类中添加一个 Stage 静态方法,供外部直接引用 Stage 。
export default class Progress extends Component { render(){ const {stage} = this.props const children = React.Children.map(this.props.children, child => { return React.cloneElement(child, {stage}) }) return( <div style={styles.progressContainer}> {children} </div> ) } }
完成上述步骤后,我们可以在任意位置动态添加任意数量的 Stage 组件:
import React, { Component } from 'react'; import Stepper from "./Stepper" class App extends Component { render() { return ( <Stepper stage={1}> <Stepper.Progress> <Stepper.Stage num={1} /> <Stepper.Stage num={2} /> <Stepper.Stage num={3} /> <Stepper.Stage num={4} /> </Stepper.Progress> <Stepper.Steps /> </Stepper> ); } } export default App;
接下来我们可以对 Steps 组件做同样的改进,但这个有点不同,因为每个子项都要被 React's Transition Group 的 Transition 组件包裹。同样是使用 Children.map() 遍历,但只有 Steps 组件的 stage 属性与子组件的 num 属性匹配时才展示该子组件。 即它们匹配时,子组件会被包裹在 Transition 组件中(ReactTransitionGroup文档解释了此目的)并在屏幕上渲染。
Transition
num
class Steps extends Component { render() { const { stage, handleClick } = this.props const children = React.Children.map(this.props.children, child => { console.log(child.props) return ( stage === child.props.num && <Transition appear={true} timeout={300} onEntering={entering} onExiting={exiting}> {child} </Transition> ) }) return ( <div style={styles.stagesContainer}> <div style={styles.stages}> <TransitionGroup> {children} </TransitionGroup> </div> <div style={styles.stageButton}> <Button disabled={stage === 4} click={handleClick}>Continue</Button> </div> </div> ); } } export default Steps;
通过在 Stepper 组件上添加相应的静态方法,我们可以按照我们想要的顺序添加任意数量的Step组件。
import React, { Component } from 'react'; import Stepper from "./Stepper" class App extends Component { render() { return ( <Stepper stage={1}> <Stepper.Progress> <Stepper.Stage num={1} /> <Stepper.Stage num={2} /> <Stepper.Stage num={3} /> </Stepper.Progress> <Stepper.Steps> <Stepper.Step num={1} text={"Stage 1"}/> <Stepper.Step num={2} text={"Stage 2"}/> <Stepper.Step num={3} text={"Stage 3"}/> <Stepper.Step num={4} text={"Stage 4"}/> </Stepper.Steps> </Stepper> ); } } export default App;
我们用一种方式就创建了非常灵活可重用的组件。现在我们可以选择多少个 stage,每个 stage 的文本和顺序,以及我们可以决定进度条在左侧还是右侧。
虽然改进了很多,但在灵活性上我们仍然受到限制! 如果我们想在 Steps 上方添加标题怎么办?
class App extends Component { render() { return ( <Stepper stage={1}> <Stepper.Progress> <Stepper.Stage num={1} /> <Stepper.Stage num={2} /> <Stepper.Stage num={3} /> </Stepper.Progress> <div> <div>Title</div> <Stepper.Steps> <Stepper.Step num={1} text={"Stage 1"}/> <Stepper.Step num={2} text={"Stage 2"}/> <Stepper.Step num={3} text={"Stage 3"}/> <Stepper.Step num={4} text={"Complete!"}/> </Stepper.Steps> </div> </Stepper> ); } } export default App;
上面这样做会破坏我们应用程序的结构,因为 Stepper.Steps 组件不再是 Stepper 组件的直接子组件,所以它无法访问 stage 属性了。
Stepper.Steps
这就是为什么 React 16.3 的发布非常重要! 到目前为止,React’s context API 还处于试验阶段,但现在它已经正式发布了!
context
在本系列的第2部分中,我将探讨如何实现 context API 以便能够在组件树中的任何位置传递属性,这样无论 Stepper.Steps 组件位于何处,它始终都能够访问 stage 属性。
The text was updated successfully, but these errors were encountered:
No branches or pull requests
原文链接:How To Master Advanced React Design Patterns: Compound Components
为了庆祝 React 16.3 的正式发布,我决定分享我最近使用的一些技术,这些技术彻底改变了我创建 React 组件的方法。因此,我能够设计出完全可重用的组件,并且可以在许多不同的环境中灵活地使用这些组件。
单击此处查看本系列的第2部分:Context API
上面的
sandbox
是一个简洁的Stepper
组件的初始代码,我将使用它来展示其中的一些技术。 就目前而言,这个组件完全正常工作,并且完全按照设计目的进行,但它缺乏灵活性。如上可视,
Stepper
组件的灵活性在stage
属性处终止;我们只能修改stage
的值来确定Stepper
组件进行到哪一步。stage
的Stepper
怎么办?stage
的内容怎么办?stage
的顺序怎么办?就目前而言,我要实现这些变化的唯一方法是完全重写组件,以相同的方式重写一个类似的组件。 但是,如果将来又要进行其他更改,那该组件又一次需要重写。 因此,让我们尝试不同的方法来重写组件,使其具有灵活性和可重用性,以应变将来任何的配置。
在本系列的第一部分中,我们将探讨一种名为“复合组件”的设计模式
使用复合组件设计模式
首先,让我们来看看
Stepper
组件。Stepper
组件有一个存储当前stage
的状态对象,一个增加stage
属性值的方法,以及一个render
方法,它返回包含2个子组件的div。目前,我们明确地将
Progress
和Steps
组件直接放在Stepper
组件中。 为了减少这种静态写法,我们可以使用props
对象动态插入子组件。在
Stepper.js
文件中使用props.children
对象替换Progress
和Steps
组件,并将它们放在App.js
中的Stepper
组件内。只需这简单的改变就给我们带来很大的收益。现在我们可以选择组件树中的哪个组件先渲染; 我们可以选择进度块是在左侧还是右侧。
但这种方法有一个问题:
Progress
和Steps
组件不能再访问stage
和handleClick
属性了。 为了让每个子组件获取它们需要的属性,我们需要手动遍历每个子组件并向其注入这些属性。 我们可以使用 react API 提供的一些辅助方法来实现。 两个方法是:Children.map()
和cloneElement()
。Children.map()
类似于Array.map()
方法。但请务必使用Children.map()
,因为children.props
具有不透明的数据结构,使得Array.map()
方法不适合此用例。cloneElement
如名称一样,它克隆这些子组件并可以注入额外的属性,最后返回新的组件。现在我们可以将
Progress
和Stage
作为子项添加到Stepper
组件中,运行效果不变。但此时我们可以决定每个组件的位置,甚至可以在左右两侧同时设置进度块。静态属性
另外一种能够提高可读性和易用性的技术就是使用类的静态属性。它允许我们直接在类上调用方法。
这是什么意思? 让我们来看看…。
首先,我们在
Stepper
组件中创建两个静态方法,并将Progress
和Steps
组件赋值给它们:那么现在,我们不需要再到处引入
Progress
和Steps
组件,而是直接从Stepper
组件中引用它们:到目前为止,我们已经创建了一个简单可读且灵活的API。那么是不是可以对
Progress
组件使用相同的技术呢? 让我们来看看......您也许已经注意到
Progress
组件与之前的Stepper
组件存在同样的问题。所以我们用props.children
对象来替换这 4 个Stage
组件并遍历子项添加所需的属性,然后在Stepper
类中添加一个Stage
静态方法,供外部直接引用Stage
。完成上述步骤后,我们可以在任意位置动态添加任意数量的
Stage
组件:接下来我们可以对
Steps
组件做同样的改进,但这个有点不同,因为每个子项都要被 React's Transition Group 的Transition
组件包裹。同样是使用Children.map()
遍历,但只有Steps
组件的stage
属性与子组件的num
属性匹配时才展示该子组件。 即它们匹配时,子组件会被包裹在Transition
组件中(ReactTransitionGroup文档解释了此目的)并在屏幕上渲染。通过在
Stepper
组件上添加相应的静态方法,我们可以按照我们想要的顺序添加任意数量的Step组件。我们用一种方式就创建了非常灵活可重用的组件。现在我们可以选择多少个 stage,每个 stage 的文本和顺序,以及我们可以决定进度条在左侧还是右侧。
虽然改进了很多,但在灵活性上我们仍然受到限制! 如果我们想在
Steps
上方添加标题怎么办?上面这样做会破坏我们应用程序的结构,因为
Stepper.Steps
组件不再是Stepper
组件的直接子组件,所以它无法访问stage
属性了。这就是为什么 React 16.3 的发布非常重要! 到目前为止,React’s
context
API 还处于试验阶段,但现在它已经正式发布了!在本系列的第2部分中,我将探讨如何实现
context
API 以便能够在组件树中的任何位置传递属性,这样无论Stepper.Steps
组件位于何处,它始终都能够访问stage
属性。The text was updated successfully, but these errors were encountered: