博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
React 设计模式和场景分析
阅读量:7013 次
发布时间:2019-06-28

本文共 4836 字,大约阅读时间需要 16 分钟。

Kygo on live

这一周连续发表了两篇关于 React 的文章:

其中涉及到 React 组件复用、轮子设计相关话题,并配合相关场景实例进行了分析。这些内容都算是 React 设计模式,一提到 Design Patterns,读者大可不必恐惧,事实上这都是 React 开发应用灵活性的体现。今天这篇文章,我们继续通过一个场景,循序渐进,通过一步步优化设计来进行加深理解。

场景介绍

页面展现

屏幕左侧大面积展现区块内容,点击 continue 按钮,切换为下条内容信息;右侧是一个导航条,指示当前区块展示信息条目。

如果看 Gif 图不过瘾,可以到 进行在线了解。

具体代码结构为:

class App extends Component {  render() {    return (        
); }}

Stepper 组件 'stage' prop 表示默认开始第几个区块,同时具用同名 'stage' 状态。stage 在这里表示左侧一个个内容区块。

handleClick 方法对 this.stata.stage 进行切换。

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 (      
); }}

我们看到,Stepper 组件包含 Progress 组件(左侧导航)以及 Steps 组件。

这样的代码运行良好,但是在复用性和灵活性上有一些问题。比如:

  • 如果我们需要切换 Progress 和 Steps 组件(左右)展示顺序怎么办?
  • 如果我们的 Stepper 需要承载更多的 stages 怎么办?
  • 如果我们需要更改某个 stage 内容怎么办?
  • 如果我们想要切换 stages 顺序该怎么办?

现有代码基础上,这些问题都可以解决。但是需要重新更改组件编写内容。如果某天又新增或者调整了需求,组件内容同样又需要改写。

接下来,我们用另一种方式实现需求,使得代码更加灵活,复用性更强。

重新设计

仔细观察 Stepper 组件:它包含了当前区块 stage,以及一个更改 stage 的方法,渲染了两个子组件。

我们使用 Function as Child Component 手段,将 Stepper 组件重构。(如果对 Function as Child Component 不熟悉,请参考我之前文章 )

如下图:

Function as Child Component 重构

Progress 和 Steps 组件不再直接出现在 Stepper 组件的 render 方法中。我们使用 this.props.children 对 Stepper 组件的所有子组件进行渲染。这样 Stepper 组件渲染的内容更加灵活。

但是仅仅这样的修改是不可能完成需求的,当用户点击 continue 按钮,stage 并不会进行切换。因为 Progress 和 Steps 组件无法再通过 props 感知 stage 和 handleClick 方法。

为了解决这个问题,我们可以手动遍历 Stepper 组件的子节点,并对相应 props 一一注入。如下代码:

const children = React.Children.map(this.props.children, child => {        return React.cloneElement(child, {stage, handleClick: this.handleClick})    })

借助 React.Children.map 进行子节点遍历,并通过 React.cloneElement 方法对子组件进行拷贝,这个方法通过第二个参数,具有添加额外 props 的能力。Stepper 组件的 render 方法只需要具体应用:

const { stage } = this.state;const children = React.Children.map(this.props.children, child => {    return React.cloneElement(child, {stage, handleClick: this.handleClick})})return (    
{children}
);

这样一来,应用又一次正确运转!

class App extends Component {  render() {    return (      
); }}

同样的手段,我们也可以应用到 Progress 组件当中。这里不再一一展开。

使用 Static Properties

值得一提的是,我们可以使用 Static Properties 增强代码的可读性。Static Properties 允许我们在 class 当中直接对方法进行调用。首先,我们在 Stepper 组件中创建两个 static 方法,并赋值给 Progress 组件和 Steps 组件:

static Progress = Progress;static Steps = Steps

现在,在 App.js 中我们可以直接:

import React, { Component } from 'react';import Stepper from "./Stepper"class App extends Component {  render() {    return (      
); }}export default App;

这样的好处体现在不用一次次地 import 进来 Progress 组件和 Steps 组件,它们都将作为 Stepper 的静态属性出现。我个人并不是很喜欢这种做法。

使用 React Transition Group

我们使用 React Transition Group 对 Steps 组件内容添加过渡动画。只有当 props.num 与 this.props.stage 相等时,区块内容设置为可见:

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 &&                
{child}
) }) return (
{children}
); }}

我们也可以给 Steps 组件添加任意个内容:

import Stepper from "./Stepper"class App extends Component {  render() {    return (        
); }}

重新设计之后,整个应用变得更加灵活,复用性更强。我们可以指定任意个 stages,每一个 stage 文本内容也可以自定义设置,同样 stages 排列顺序等都可以随意搭配。

重构代码以及效果可以访问查看。

思考及待续

如果你觉得上述代码完美无懈可击,那显然想简单了。需求是变化多端的,如果我们想在 Steps 区块上,加一个大标题呢?

class App extends Component {  render() {    return (        
Title
); }}

如图,

加入标题

这样一来,Stepper.Steps 组件再也不是 Stepper 组件的直接唯一子节点了,那预期之中的 props 自然又一次无法取得!

问题也不仅仅于此。笔者本人不是很喜欢类似 React.cloneElement 顶层 API,除了偏好以外,也有一个难以规避的问题:在使用 React.cloneElement 扩充 props 时,如果出现 props 命名冲突怎么办?

比如一个 <input> 遇见了命名为 value 的 prop,后果可想而知。

那么问题来了,是否有更优雅高效的方法解决上述问题?或者,是否有更好的方式,实现更灵活的设计?

答案一定是有的,我将会留在下一篇文章进行讲解。

本文源于:,部分内容有改动。

广告时间:

如果你对前端发展,尤其对 React 技术栈感兴趣:我的新书中,也许有你想看到的内容。关注作者 ,新书出版将会有送书活动。

Happy Coding!

PS: 作者  和  欢迎各种形式交流!

我的其他几篇关于React技术栈的文章:

转载地址:http://vhqtl.baihongyu.com/

你可能感兴趣的文章
JAVA代码注释
查看>>
我也说 IEnumerable,ICollection,IList,List之间的区别
查看>>
RDBMS下scott.sql
查看>>
.Net关于日期的
查看>>
leetcode_question_115 Distinct Subsequences
查看>>
Android Developers:在命令行构建和运行
查看>>
firefox 不识别background-position-y / background-position-x
查看>>
分析函数调用关系图(call graph)的几种方法
查看>>
Dynamic Web Module 3.0 requires Java 1.6 or newer
查看>>
11.0592M晶振与12M晶振
查看>>
玩转docker
查看>>
Web Service学习笔记
查看>>
[转帖]cocos2D-X源码分析之从cocos2D-X学习OpenGL(3)----BATCH_COMMAND
查看>>
A380上11万一张的机票什么享受?来看看
查看>>
LeetCode: 103_Binary Tree Zigzag Level Order Traversal | 二叉树Zigzag层次遍历 | Medium
查看>>
JUnit单元测试中的setUpBeforeClass()、tearDownAfterClass()、setUp()、tearDown()方法小结
查看>>
Java程序猿JavaScript学习笔记(2——复制和继承财产)
查看>>
ubuntu15.10下编译安装wine1.8 rc4
查看>>
Hello,HTML 到 HTML5
查看>>
jquery获取节点的时候获取包含自己在内的HTML标签
查看>>