Javascript ReactJS和父/子组件:如何构建这个概念日历?

Javascript ReactJS和父/子组件:如何构建这个概念日历?,javascript,reactjs,hierarchy,uicomponents,Javascript,Reactjs,Hierarchy,Uicomponents,我正在做一个ReactJS项目,这是一个日历,作为我所追求的概念的一个示例,其中所需的行为是显示: January 1, 2016 Activity 1 Activity 2 January 2, 2016 Activity 3 January 4, 2016 Activity 4 Activity 5 January 8, 2016 Activity 6 换句话说,对于有趣的部分,标题的日期,除了简单的CSS之外,打算在条目之前显示一次,在第一个条目之前显示一天

我正在做一个ReactJS项目,这是一个日历,作为我所追求的概念的一个示例,其中所需的行为是显示:

January 1, 2016
  Activity 1
  Activity 2

January 2, 2016
  Activity 3

January 4, 2016
  Activity 4
  Activity 5

January 8, 2016
  Activity 6
换句话说,对于有趣的部分,标题的日期,除了简单的CSS之外,打算在条目之前显示一次,在第一个条目之前显示一天,然后在第一个条目上方不显示一天

Facebook/React/Flux有效地向共享可变状态宣战,而一个明显但错误的方法是通过共享可变状态来解决这个问题。如果一个子组件的显示方式应根据其自身状态的不同而有所不同,那么如何更好或最好地处理上述问题呢。这可能不需要共享可变状态,但我不知道最惯用的方法是什么

-澄清-

@SeanO对这个问题的第一个评论是正确的,我相信这是一个解决这类问题的算法。几年前,我在一个漂亮、粗糙的Perl CGI上做了类似的事情

但我真正想要的不是什么样的算法适合这里,而是如何在ReactJS中适当地实现它

-补充澄清-

在看了您的解决方案后,我有一个问题:如前所述,它适用于静态数据。我感兴趣的是显示用户界面的变化,不仅是因为明天变成今天,在职者被从显示中拉出来,而是人们可以在日历中添加新数据或以其他方式更改它的东西。使用附带的工具来持久化用户发起的更改是很容易的,但我想知道这一点。答案是否只是做同样的事情,但将所有事情都变为状态,或者采用不同的方法是否合适

根据我目前的理解,我可以完成并实施它,也许很糟糕,但在这里我想知道什么解决方案真正获胜@WiktorKozlik在回答中的开场白正是我想知道的:如果一个组件需要知道的信息多于它所接收到的数据才能呈现自己,那么这就表明该组件有问题


谢谢,

如果一个组件需要知道比它自己渲染所接收的数据更多的信息,那么这就表明该组件有问题。我想知道您是否尝试将组件与活动中的数据形状相匹配,而不是从UI设计中派生组件结构

例如,用下面的结构表示UI可以让您考虑每个组件需要哪些数据,而不用担心算法

<Calendar activities={...}>
    <CalendarDay day="2016-01-01" activities={[activity1, activity2]}>
        January 1, 2016
        <Activity activity={activity1}>
            Activity 1
        </Activity>
        <Activity activity={activity2}>
            Activity 2
        </Activity>
    </CalendarDay>
    <CalendarDay day="2016-01-02" activities={[activity3]}>
        January 2, 2016
        <Activity activity={activity3}>
            Activity 3
        </Activity>
    </CalendarDay>
    ...
</Calendar>

如果一个组件需要知道更多的数据才能呈现自己,那么这就是该组件有问题的迹象。我想知道您是否尝试将组件与活动中的数据形状相匹配,而不是从UI设计中派生组件结构

例如,用下面的结构表示UI可以让您考虑每个组件需要哪些数据,而不用担心算法

<Calendar activities={...}>
    <CalendarDay day="2016-01-01" activities={[activity1, activity2]}>
        January 1, 2016
        <Activity activity={activity1}>
            Activity 1
        </Activity>
        <Activity activity={activity2}>
            Activity 2
        </Activity>
    </CalendarDay>
    <CalendarDay day="2016-01-02" activities={[activity3]}>
        January 2, 2016
        <Activity activity={activity3}>
            Activity 3
        </Activity>
    </CalendarDay>
    ...
</Calendar>

通常,不依赖内部状态的可重用组件的关键是使用属性。数据从顶级组件向下流入组合的子级。与在其他情况下使用SRP的方式大致相同,将UI分解为职责会有很大帮助

例如,根据您的规范,似乎有三个嵌套的组件/职责:

日历组件,负责决定渲染哪几天以及如何进行布局。 Day组件,负责呈现日期标题和包含的活动。 活动组件,负责显示单个活动。 让我们浏览一下构建此组件的过程。完成的示例是

由于您没有指定开始使用的数据格式,因此让我们使用一个简单的对象数组,每个对象都有一个日期和一个活动名称:

风险值活动=[ {日期:2016-01-01,名称:活动1}, {日期:2016-01-01,名称:活动2}, {日期:2016-01-02,名称:活动3}, {日期:2016-01-04,名称:活动4}, {日期:2016-01-04,名称:活动5}, {日期:2016-01-08,名称:活动6}, ]; 日历组件 我们的顶级日历组件将采用以下格式的数据作为输入:

var ActivitiesCalendar=React.createClass{ 道具类型:{ 活动:React.PropTypes.arrayOf 反应{ 日期:React.PropTypes.string.isRequired, 名称:React.PropTypes.string.isRequired } .需要 }, // ... }; 日历应该为包含一个或多个活动的每一天呈现一天条目。让我们将日/活动对数组转换为一个对象,该对象将日映射为当天发生的活动数组。我在这里使用了一些通过JSX transpiler提供给我们的ES6语法,但您不必:

渲染{ 风险值活动 ByDay=\ uDay.chainthis.props.activities .groupByactivity=>activity.day .mapObjectactivities=>\u0.Activities,名称 价值 返回{this.renderActivitiesactivitiesByDay}; }, //活动ByDay看起来像: // { // 2016-01-01: [ //活动1, //活动2 // ], // 2016-01-02: [ //活动3 // ], // 2016-01-04: [ //活动4, //活动5 // ], // 2016-01-08: [ //活动6 // ] // } 请注意,activitiesByDay的结果值与我们要显示的UI非常相似。在React中,将数据转换为映射到UI结构的格式通常是处理UI组合的一种好方法

我们将activitiesByDay传递给renderActivities,renderActivities通过迭代对象的键、对它们进行排序并提取每天的活动来确定渲染的日期和顺序

RenderActivitysActivitiesByDay{ //compareDays按升序排序日期 var days=Object.KeyActivitiesByDay.sortcompareDays; return days.mapday=>{ 回来 ; }; } 请注意,日历组件没有假设一天应该是什么样子;它将此决定委托给CalendarDay,只需传递当天以及当天的活动列表

日组件 类似地,日历日组件会列出日期标题和活动本身,但也不会决定活动的实际外观。它将此委托给CalendarActivity组件:

var CalendarDay=React.createClass{ 道具类型:{ 日期:React.PropTypes.string.isRequired, 活动:React.PropTypes.arrayOfReact.PropTypes.string.isRequired }, 渲染{ 回来 {this.formattedDatethis.props.day} {this.props.activities.mapthis.renderActivity} ; }, 格式化日期{ 返回momentdate.formatMMMM DD,YYYY; }, 渲染活性{ 回来 } }; 我使用显示格式良好的日期标题;否则,此模式看起来与ActivitiesCalendar组件非常相似

活动组件 最后,活动组件仅显示本例中的活动,仅显示一个字符串:

var CalendarActivity=React.createClass{ 道具类型:{ 活动:React.PropTypes.string.isRequired }, 渲染{ 返回{this.props.activity}; } }; 结论 在中,我们可以看到数据从日历的顶级组件流到作为属性的日期和活动组件。在每个阶段,我们将数据分解成更小的部分,并将其中的一部分发送到另一个组件。每个组件组成更小、更集中的组件,直到我们在原始DOM节点结束

还要注意的是,每个组件都可以独立,唯一的接口是通过组件的属性,并且组件不耦合到它们在组件层次结构中的位置。这对于真正可重用的组件非常重要。例如,我们可以通过查看CalendarDay的propTypes并以其声明的形状向其发送一些数据来单独呈现一个日历日:

//CalendarDay的PropType是: // //日期:React.PropTypes.string.isRequired, //活动:React.PropTypes.arrayOfReact.PropTypes.string.isRequired var日= React.renderday,document.body; 您还可以利用其他高级技术;例如,在使用Flux模式时,通常会让组件进入数据存储区以获取自己的数据。这可以简化数据获取,因为不再需要将数据从父级传递到子级再传递到孙子级等,但值得注意的是,在这种情况下,组件的可重用性较差

[Update]就动态数据而言,React的标准答案是将共享数据向上移动到某个父组件,并使其他组件需要修改该组件上的数据调用函数;他们通过将这些函数作为道具传递来访问它们。例如,您可能有

var Application=React.createClass{ getInitialState{ 返回{activities:myActivities}; }, 渲染{ 回来 }, handleActivityAddnewActivity{ var newData=。。。; this.setState{activities:newData}; } }; 需要能够将活动添加到日历的ActivityCalendar的子级还应收到对提供的OnActivityAddprop的引用。对于其他操作,如onActivityRemove等,您可能会重复上述操作。需要注意的是,日历组件不会直接修改此操作上的任何内容。它们仅通过调用提供的某些函数来修改数据。请注意,这正是类似的工作原理

任何做任何有趣事情的应用程序都会在某个地方发生某种状态突变;重点不是避免可变状态,而是最小化它,或者至少控制它 他们调用一些提供的方法来控制它,并使它的修改易于理解,而不是子组件更新某些对象的属性


这就是flux模式开始发挥作用的地方:flux将可变数据集中到存储中,并且只允许通过事件修改该数据,这样组件就可以与改变状态的实现细节分离,但不会强制您将属性传递到长组件层次结构中。也就是说,对于可重用组件,通常最好将函数引用作为属性,这样组件就不会与特定的应用程序或通量实现绑定。

通常,不依赖内部状态的可重用组件的关键是使用属性。数据从顶级组件向下流入组合的子级。与在其他情况下使用SRP的方式大致相同,将UI分解为职责会有很大帮助

例如,根据您的规范,似乎有三个嵌套的组件/职责:

日历组件,负责决定渲染哪几天以及如何进行布局。 Day组件,负责呈现日期标题和包含的活动。 活动组件,负责显示单个活动。 让我们浏览一下构建此组件的过程。完成的示例是

由于您没有指定开始使用的数据格式,因此让我们使用一个简单的对象数组,每个对象都有一个日期和一个活动名称:

风险值活动=[ {日期:2016-01-01,名称:活动1}, {日期:2016-01-01,名称:活动2}, {日期:2016-01-02,名称:活动3}, {日期:2016-01-04,名称:活动4}, {日期:2016-01-04,名称:活动5}, {日期:2016-01-08,名称:活动6}, ]; 日历组件 我们的顶级日历组件将采用以下格式的数据作为输入:

var ActivitiesCalendar=React.createClass{ 道具类型:{ 活动:React.PropTypes.arrayOf 反应{ 日期:React.PropTypes.string.isRequired, 名称:React.PropTypes.string.isRequired } .需要 }, // ... }; 日历应该为包含一个或多个活动的每一天呈现一天条目。让我们将日/活动对数组转换为一个对象,该对象将日映射为当天发生的活动数组。我在这里使用了一些通过JSX transpiler提供给我们的ES6语法,但您不必:

渲染{ var activitiesByDay=\ uu0.chainthis.props.activities .groupByactivity=>activity.day .mapObjectactivities=>\u0.Activities,名称 价值 返回{this.renderActivitiesactivitiesByDay}; }, //活动ByDay看起来像: // { // 2016-01-01: [ //活动1, //活动2 // ], // 2016-01-02: [ //活动3 // ], // 2016-01-04: [ //活动4, //活动5 // ], // 2016-01-08: [ //活动6 // ] // } 请注意,activitiesByDay的结果值与我们要显示的UI非常相似。在React中,将数据转换为映射到UI结构的格式通常是处理UI组合的一种好方法

我们将activitiesByDay传递给renderActivities,renderActivities通过迭代对象的键、对它们进行排序并提取每天的活动来确定渲染的日期和顺序

RenderActivitysActivitiesByDay{ //compareDays按升序排序日期 var days=Object.KeyActivitiesByDay.sortcompareDays; return days.mapday=>{ 回来 ; }; } 请注意,日历组件没有假设一天应该是什么样子;它将此决定委托给CalendarDay,只需传递当天以及当天的活动列表

日组件 类似地,日历日组件会列出日期标题和活动本身,但也不会决定活动的实际外观。它将此委托给CalendarActivity组件:

var CalendarDay=React.createClass{ 道具类型:{ 日期:React.PropTypes.string.isRequired, 活动:React.PropTypes.arrayOfReact.PropTypes.string.isRequired }, 渲染{ 回来 {this.formattedDatethis.props.day} {this.props.activities.mapthis.renderActivity} ; }, 格式化日期{ 返回momentdate.formatMMMM DD,YYYY; }, 渲染活性{ 回来 } }; 我使用显示格式良好的日期标题;否则,此模式看起来与ActivitiesCalendar组件非常相似

活动组件 最后,活动组件仅显示本例中的活动,仅显示一个字符串:

var CalendarActivity=React.createClass{ 道具类型:{ 活动:React.PropTypes.string.isRequired }, 渲染{ 重新 转动{this.props.activity}; } }; 结论 在中,我们可以看到数据从日历的顶级组件流到作为属性的日期和活动组件。在每个阶段,我们将数据分解成更小的部分,并将其中的一部分发送到另一个组件。每个组件组成更小、更集中的组件,直到我们在原始DOM节点结束

还要注意的是,每个组件都可以独立,唯一的接口是通过组件的属性,并且组件不耦合到它们在组件层次结构中的位置。这对于真正可重用的组件非常重要。例如,我们可以通过查看CalendarDay的propTypes并以其声明的形状向其发送一些数据来单独呈现一个日历日:

//CalendarDay的PropType是: // //日期:React.PropTypes.string.isRequired, //活动:React.PropTypes.arrayOfReact.PropTypes.string.isRequired var日= React.renderday,document.body; 您还可以利用其他高级技术;例如,在使用Flux模式时,通常会让组件进入数据存储区以获取自己的数据。这可以简化数据获取,因为不再需要将数据从父级传递到子级再传递到孙子级等,但值得注意的是,在这种情况下,组件的可重用性较差

[Update]就动态数据而言,React的标准答案是将共享数据向上移动到某个父组件,并使其他组件需要修改该组件上的数据调用函数;他们通过将这些函数作为道具传递来访问它们。例如,您可能有

var Application=React.createClass{ getInitialState{ 返回{activities:myActivities}; }, 渲染{ 回来 }, handleActivityAddnewActivity{ var newData=。。。; this.setState{activities:newData}; } }; 需要能够将活动添加到日历的ActivityCalendar的子级还应收到对提供的OnActivityAddprop的引用。对于其他操作,如onActivityRemove等,您可能会重复上述操作。需要注意的是,日历组件不会直接修改此操作上的任何内容。它们仅通过调用提供的某些函数来修改数据。请注意,这正是类似的工作原理

任何做任何有趣事情的应用程序都会在某个地方发生某种状态突变;关键不是要避免可变状态,而是要最小化它,或者至少控制它并使对它的修改可以理解,而不是子组件更新某些对象的属性,它们调用一些提供的方法


这就是flux模式开始发挥作用的地方:flux将可变数据集中到存储中,并且只允许通过事件修改该数据,这样组件就可以与改变状态的实现细节分离,但不会强制您将属性传递到长组件层次结构中。也就是说,对于可重用组件,通常最好坚持将函数引用作为属性,这样组件就不会与特定的应用程序或通量实现绑定。

New在这里做出反应,但还没有得到通量,但是如果您只是迭代活动列表,并对每个后续活动进行日期差异,如果不同,显示一个新的日期标题组件?这里有新的反应,没有变化,但是如果你只是在你的活动列表上迭代,并与每个后续活动进行日期差异,如果不同,显示一个新的日期标题组件呢?我在这里看到了pixie dust的自由帮助。谢谢你能看看我在帖子上新加的说明吗?谢谢,@JonathanHayward问得好;我在底部更新了我的答案。我在这里看到了一个自由的帮助。谢谢你能看看我在帖子上新加的说明吗?谢谢,@JonathanHayward问得好;我已经在底部更新了我的答案。你能阅读我在帖子上新增的澄清吗?谢谢,XML来自哪里?这看起来像是经典的JSX,但关于JSX的指导是,如果您觉得JSX有帮助,我们非常欢迎您使用它,但您提供的唯一脚本是JavaScript(可能是渲染的JavaScript)[不包括CoffeeScript或任何其他bazaar产品]。对于生产站点,访问者不应该看到您的原始JSX。。。这看起来像是呼吁使用代码生成工具来创建JSX,这可能是可行的,但Facebook非常确定这不是一个成功的解决方案。它应该传达UI的结构以及每个组件需要哪些数据@BinaryMuse对几乎相同的解决方案进行了更详细的介绍。你能阅读我在帖子中新添加的说明吗?谢谢,XML来自哪里?这看起来像是经典的JSX,但是关于JSX的指导是,如果您觉得JSX有帮助,那么我们非常欢迎您使用它,但是我们给了Jav
aScript,可能是呈现的JavaScript,作为您提供的唯一脚本[不包括CoffeeScript或任何其他bazaar产品]。对于生产站点,访问者不应该看到您的原始JSX。。。这看起来像是呼吁使用代码生成工具来创建JSX,这可能是可行的,但Facebook非常确定这不是一个成功的解决方案。它应该传达UI的结构以及每个组件需要哪些数据@BinaryMuse详细介绍了几乎相同的解决方案。