前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >用react的方式来思考

用react的方式来思考

作者头像
一粒小麦
发布2019-07-18 17:11:49
1.8K0
发布2019-07-18 17:11:49
举报
文章被收录于专栏:一Li小麦一Li小麦

接下来引用自己于2016年12月15写的 笔记https://www.cnblogs.com/djtao/p/6181807.html

用react的方式来思考

本文主要内容来自React官方文档中的“Thinking React”部分,总结算是又一篇笔记。主要介绍使用React开发组件的官方思路。代码内容经笔者改写为较熟悉的ES5语法。

React——在我们看来,是用javascript快速开发大型web应用的捷径。这在Facebook和Instagram实践中得到了证实。



零 任务描述

假设我们已经拿到了一个蹩脚设计师给的设计稿:

从后端返回来的一组json数据包括商品类,商品名,价格和库存:

代码语言:javascript
复制
[
    {
        "category": "Sporting Goods",
        "price": "$49.99",
        "stocked": true,
        "name": "Football"
    },
    {
        "category": "Sporting Goods",
         "price": "$9.99",
         "stocked": true,
         "name": "Baseball"
    },
    {
        "category": "Sporting Goods",
        "price": "$29.99",
        "stocked": false,
        "name": "Basketball"
    },
    {
        "category": "Electronics",
        "price": "$99.99",
        "stocked": true,
        "name": "iPod Touch"
    },
    {
        "category": "Electronics",
        "price": "$399.99",
        "stocked": false,
        "name": "iPhone 5"
    },
    {
        "category": "Electronics",
        "price": "$199.99",
        "stocked": true,
        "name": "Nexus 7"
    }
]

我们在根目录下创建一个json.json文件。放入这些信息,模拟从后台获取的数据。 需求:实现商品的展示,筛选功能,

第一步:将UI分解为组件层次结构

你要做的第一件事就是在纸上画出每个子组件,并逐一给它们命名。 或者更简单点,把设计稿psd图层组名就可以作为React组件的名字——从逻辑上说,他俩基本是一回事。

组件应该如何分类嵌套? 秘籍在于:一个组件应该只做一件事。 跟英译中一样,如果句子最终变得又臭又长,那你就应该把它分解成更精悍的短句,短又怎么了?

向用户反馈一个JSON里的数据信息时,你会发现,如果你的json框架搭的没问题,则你的UI也(或者说组件结构)会将很好地映射出来。 用户界面和数据模型始终遵循相同的信息架构——意味着把UI分割为组件是一件轻松的事。这里可以留意下给出的json数据排列方式——同一个 category的数据都放一块了。这为后文的生成商品类提供了极大的方便。

如下图,你看到在这个APP里有5个组件。我们着重标出了每个组件应该展示什么数据。

  • UI面板(橙色):包含完整的应用。
  • 搜索框(蓝色):接收用户输入信息
  • 商品面板(绿色):用于展示按照一定规则过滤后的数据。
  • 商品类别目录(青色):显示每个商品类别的标题
  • 商品信息(红色):显示每个商品

留意到商品列表,你会发现表头(包含“名称”和“价格”的标签)可以不是组件————这根据自身习惯因人而异。本例中,我们把它划归到商品面板,是因为它是数据呈现的一部分。 然而,假使这个表头很复杂(比如说,我要对它实现点击排序),那它肯定得独立划分为一个表头组件。

既然我们已经确定了组件,接下来就是安排层次结构。这也很简单:在层次结构中,需要关联其它组件才能显示的组件,就是子组件。 如果你还是不得要领,那么看这个。

  • APP面板
  • 搜索框
  • 商品面板
    • 商品类别目录
    • 商品

第二步,实现静态的代码

思考:自上而下的数据流

首先思考数据流

商品目录实际上是分辨data的category属性,通过遍历把不同属性放到数组内就行了。 商品信息也是遍历。如果遇到stocked属性为false(没库存),就把该商品名显示为红色。 整个架构应该是在ajax方法的回调中实现。这里使用jquery的 getJSON方法。

得到以下代码:

代码语言:javascript
复制
$.getJSON('json.json',function(data){
        //console.log(data);

        var App=React.createClass({
            render:function(){
                return (
                    <div>
                        <SearchBar/>
                        <ProductTable products={this.props.products}/>
                    </div>
                );//把获取到的data传入到商品面板中作为属性!
            }
        });

        var SearchBar=React.createClass({
            render:function(){
                return (
                    <div>
                        <input type="text"/><br/>
                        <input type="checkbox"/>只显示有库存的商品
                    </div>
                );
            }
        });

        var ProductTable=React.createClass({
            render:function(){
                var rows=[];
                var lastCategory=null;
                this.props.products.forEach(function(product){//遍历this.props.products
                    if(product.category!==lastCategory){//如果该对象的商品目录不是上一个商品目录,加到数组row中去
                        rows.push(<ProductCategory name={product.category} key={product.category}/>);
                    }//就是搜集不同商品的类别!
                    //接下来push一个商品行,把该product对象作为ProductsList的子属性
                    rows.push(<PorductInfo product={product} key={product.name} />);
                    lastCategory=product.category;
                });
                return (
                    <table>
                        <thead><tr>
                            <td>商品名</td>
                            <td>价格</td>
                        </tr></thead>
                        <tbody>
                        {rows}
                        </tbody>
                    </table>
                );//注意表格结构必须完整,不得简写。
            }
        });

        var ProductCategory=React.createClass({//商品目录
            render:function(){
                return (
                    <tr><td>{this.props.name}</td></tr>
                )
            }
        });

        var PorductInfo=React.createClass({//商品信息
            render:function(){
                var name=null;
                if(!this.props.product.stocked){//如果没库存,显示为红色
                    name=
                        <span style={{color:"red"}}>{this.props.product.name}</span>
                }else{
                    name=
                        <span>{this.props.product.name}</span>
                }

                return (
                    <tr>
                        <td>{name}</td>
                        <td>{this.props.product.price}</td>
                    </tr>
                );
            }
        });



        ReactDOM.render(
            <App products={data}/>,
            document.getElementById('example')
        );

    });

效果大概是这样:

静态实现的回顾

迄今我们完成了最为简单的部分——根据数据模型渲染你的UI,封装完了的组件层次结构。然而这还没有任何交互——差不多可以实现交互功能的时候了。 但在此之前,最好明确以下几个问题。

  • 静态渲染和交互实现最好分离开来写。写一个静态的版本可能要打很多代码,而不用什么想东西;添加交互并不需要太多代码,但是你需要大量思考。
  • 静态版本的应用,父到子组件间的数据交流是通过用 props来传递的。如果你已经熟悉状态(state)的概念,那么需要记住:*对于静态版本完全不必要使用state。 状态针对的是交互————所谓“数据可以随时间改变”的东西。 *
  • 你可以按照自顶向下或自底向上的方式来构建你的组件——都没问题。 在简单的的demo中,自上而下通常更加容易,但大的项目里面,自下而上构建更方便测试。

小结:完成了静态版本,这个组件就是可复用的了。在本文这个例子的静态版本中,组件只有一个 render()方法,组件结构的顶部(App)以data为支撑。 如果你改变data的内容再刷新,UI将被更新。没有什么复杂的改变。 React的单向数据流(单向绑定)保持所有数据内容的模块化和效率。


第三步,找到最小的(且完整的)的UI状态!

触发你的底层数据改变。最好的方法就是 state。 而让UI交互起来的第一步奥义在于:不要重复!不要重复!不要重复!(Don't Reapeat Yourself)。你得明确你的需求,需要哪些状态——不必要的通通删掉。比方说,你做一个待办事项列表(TODO List),完全没必要给你的待办事项各自设置单独状态——相反,把它们放到一个数组里就可以了。

回顾我们案例中的所有交互元素,它们包括:

  • 原始呈现的商品列表
  • 搜索框内的内容
  • 复选框是否被点选
  • 过滤后的商品列表

让我们逐一思考,哪一个可以作为状态——对每个交互元素都得问三个问题:

  1. 它是通过父级组件的 props传进来的吗?,如果是,这个交互元素可能不是状态。
  2. 它随着时间的推移依然保持不变吗? 如果是,它可能不是状态。
  3. 你能可以根据组件的props和其它的state计算出来吗? 如果可以,它绝对不是状态。

在这个简单的demo中, 原始呈现的商品列表是通过 props传进来的。所以可以直接判断它不是状态。 搜索框复选框的内容不可能通过计算得到,而且可以随时间而改变——它们是状态。至于 过滤后的商品列表,它是根据搜索框和复选框的内容而计算得出的结果,所以它不是状态。

因此,我们得出,底层的状态就两个:

  • 搜索框的内容
  • 复选框是否被点选

第四步:状态放哪里?

交互实现的第二步就是:找到哪个是可变的组件,或者是拥有状态的组件。记住,React是单向数据流,父级组件通常无法知道子组件拥有哪些状态——最有挑战性的地方就在于此。你可以参照以下思路,对每个交互元素的状态从三个方面考虑:

  • 确定每个组件是不是依赖于状态?
  • 找到共同的先代组件(所有需要状态子组件的共同祖先)。
  • 常见的组件所有者或另一个更高层次结构的组件。 注:如果你找不到一个有值得拥有状态的组件,可以创建一个调试用的新组件,让它拥有所有状态,并把它加到常见所有者组件的上层。

个人以为其实主要考虑第三个就差不多了。接下来把这一策略用于本文案例:

  • 商品面板( ProductTable)展示商品内容时,基于搜索框( SearchBar)和复选框的状态
  • App是所有组件(包括它自己)的共同所有者。
  • 在理论上上,搜索框和复选框的状态放App里是有意义的。

好了,所以我们决定,状态都放App里。 接着。把这两个状态通过 props传进搜索框 SearchBar和商品面板 ProductTable。最后,根据相应的props值,渲染搜索框的文本内容,并对商品内容执行过滤。

代码语言:javascript
复制
$.getJSON('json.json',function(data){
        //console.log(data);

        var App=React.createClass({
            getInitialState:function(){
                return {//存放输入框和复选框的状态,默认为空。
                    filterText:'',
                    bStocked:false
                }
            },
            render:function(){

                return (
                    <div>
                        <SearchBar
                            filterText={this.state.filterText}
                            bStocked={this.state.bStocked}
                        />
                        <ProductTable
                            filterText={this.state.filterText}
                            bStocked={this.state.bStocked}
                            products={this.props.products}
                        />
                    </div>
                );//把获取到的data传入到商品面板中作为属性!
            }
        });

        var SearchBar=React.createClass({
            render:function(){
                console.log(this.props.bStocked)
                return (
                    <div>
                        <input value={this.props.filterText} type="text"/><br/>
                        <input checked={this.props.bStocked} type="checkbox"/>只显示有库存的商品
                    </div>
                );
            }
        });

        var ProductTable=React.createClass({
            render:function(){

                var rows=[];
                var lastCategory=null;
                //执行indexOf必须先存下来,否则识别不了this
                var str=this.props.filterText;
                var bCheck=this.bStocked;
                this.props.products.forEach(function(product){//遍历this.props.products
                    if (product.name.indexOf(str)===-1||(!product.stocked&&bCheck)){
                        return;
                    }//满足两个判断条件,直接跳出,执行下一个遍历:
                    //1.如果搜索框搜不到
                    //2.如果数据显示库存为false且点选了“只看有库存的商品”
                    if(product.category!==lastCategory){//如果该对象的商品目录不是上一个商品目录,加到数组row中去
                        rows.push(<ProductCategory name={product.category} key={product.category}/>);
                    }//就是搜集不同商品的类别!
                    //接下来push一个商品行,把该product对象作为ProductsList的子属性
                    rows.push(<PorductInfo product={product} key={product.name} />);
                    lastCategory=product.category;
                });
                return (
                    <table>
                        <thead><tr>
                            <td>商品名</td>
                            <td>价格</td>
                        </tr></thead>
                        <tbody>
                        {rows}
                        </tbody>
                    </table>
                );//注意表格结构不得简写。
            }
        });

        var ProductCategory=React.createClass({//商品目录
            render:function(){
                return (
                    <tr><td>{this.props.name}</td></tr>
                )
            }
        });

        var PorductInfo=React.createClass({//商品信息
            render:function(){
                var name=null;
                if(!this.props.product.stocked){//如果没库存,显示为红色
                    name=
                        <span style={{color:"red"}}>{this.props.product.name}</span>
                }else{
                    name=
                        <span>{this.props.product.name}</span>
                }

                return (
                    <tr>
                        <td>{name}</td>
                        <td>{this.props.product.price}</td>
                    </tr>
                );
            }
        });

第五步:让数据反向流起来

到目前为止,这个应用已经完成的差不多了。它有正确的 propsstate。 现在是时候来支持数据流动的另一种方式:底层数据把信息反馈到上层。 React让数据流一目了然,使人容易理解程序是如何工作的,但它比起传统的双向数据绑定实现,你确实还得多打一些代码。

怎么好意思说应用已经完成得差不多了呢?由于受到顶层state的影响。输入框完全不能键入内容,复选框也是点选不了,简直是在愚弄用户——但这是故意的——从React的价值取向来说,输入的内容必须从状态的所有者 App传入。 试想接下来要发生什么。当用户输入内容,触发onChange。 SearchBar将通过回调传递信息给 App,然后app根据回调的信息用 this.setState()来刷新状态。

要明白一个原理:用户并不是不能输入东西,只是输入后被被顶层状态给挡住了。

思路:

  1. 我在App中设置一个 handleUserInput方法,此方法有两个参数,传入的两个参数将分别被设置为 App状态中的 filterTextbStocked的值。
  2. 把这个 handleUserInput方法作为一个 props属性(在此命名为 onUserInput)传进子组件里边去!
  3. 用户输入时,用一个ref值把用户输入内容存入到SearBar的一个私有属性比如 this.filterTextInput中。在ES6语法下简写确实比较方便:
代码语言:javascript
复制
ref={(input) => this.filterTextInput = input}
  1. 然后onChange事件激活 this.props.onUserInput(),把你用ref记录存下来的值作为参数给传进去。就成功影响App的state。

全部代码如下:

代码语言:javascript
复制
$.getJSON('json.json',function(data){
        //console.log(data);

        var App=React.createClass({
            getInitialState:function(){
                return {//存放输入框和复选框的状态,默认为空。
                    filterText:'',
                    bStocked:false
                }
            },
            handleUserInput:function(filterText, bStocked) {
                this.setState({
                    filterText: filterText,
                    bStocked: bStocked
                });
            },
            render:function(){

                return (
                    <div>
                        <SearchBar
                            filterText={this.state.filterText}
                            bStocked={this.state.bStocked}
                            onUserInput={this.handleUserInput}
                        />
                        <ProductTable
                            filterText={this.state.filterText}
                            bStocked={this.state.bStocked}
                            products={this.props.products}
                        />
                    </div>
                );//把获取到的data传入到商品面板中作为属性!
            }
        });


        var SearchBar=React.createClass({
            handleChange:function(){
                this.props.onUserInput(
                  this.filterTextInput.value,
                  this.bStockedInput.checked
                );
            },
            render:function(){
                //console.log(this.props.bStocked)
                return (
                    <div>
                        <input
                            value={this.props.filterText}
                            type="text"
                            ref={(input) => this.filterTextInput = input}
                            onChange={this.handleChange}
                        />
                        <br/>
                        <input
                            checked={this.props.bStocked}
                            type="checkbox"
                            ref={(input) => this.bStockedInput = input}
                            onChange={this.handleChange}
                        />只显示有库存的商品
                    </div>
                );//ref={(input) => this.filterTextInput = input}表示把所有输入的内容放到ref属性中
            }
        });

        var ProductTable=React.createClass({
            render:function(){

                var rows=[];
                var lastCategory=null;
                //执行indexOf必须先存下来,否则识别不了this
                var str=this.props.filterText;
                var bCheck=this.props.bStocked;
                this.props.products.forEach(function(product){//遍历this.props.products
                    if (product.name.indexOf(str)===-1||(!product.stocked&&bCheck)){
                        return;
                    }//满足两个判断条件,直接跳出,执行下一个遍历:
                    //1.如果搜索框搜不到
                    //2.如果数据显示库存为false且点选了“只看有库存的商品”
                    if(product.category!==lastCategory){//如果该对象的商品目录不是上一个商品目录,加到数组row中去
                        rows.push(<ProductCategory name={product.category} key={product.category}/>);
                    }//就是搜集不同商品的类别!
                    //接下来push一个商品行,把该product对象作为ProductsList的子属性
                    rows.push(<PorductInfo product={product} key={product.name} />);
                    lastCategory=product.category;
                });
                return (
                    <table>
                        <thead><tr>
                            <td>商品名</td>
                            <td>价格</td>
                        </tr></thead>
                        <tbody>
                        {rows}
                        </tbody>
                    </table>
                );//注意表格结构不得简写。
            }
        });

        var ProductCategory=React.createClass({//商品目录
            render:function(){
                return (
                    <tr><td>{this.props.name}</td></tr>
                )
            }
        });

        var PorductInfo=React.createClass({//商品信息
            render:function(){
                var name=null;
                if(!this.props.product.stocked){//如果没库存,显示为红色
                    name=
                        <span style={{color:"red"}}>{this.props.product.name}</span>
                }else{
                    name=
                        <span>{this.props.product.name}</span>
                }

                return (
                    <tr>
                        <td>{name}</td>
                        <td>{this.props.product.price}</td>
                    </tr>
                );
            }
        });



        ReactDOM.render(
            <App products={data}/>,
            document.getElementById('example')
        );
    });

效果:

好了,功德圆满了。文档说,“ it's really just a few lines of code”就实现了这个UI。但我把文档用ES5语法重写,去掉空行,注释,也估计要100多行。为此结语是这么说的:

尽管写的比你平时要多一点,但是记住:代码读出来的价值远大于写出来的价值——况且React还那么好读。当你构建一个大型的组件库,你会欣赏这种明朗的,模块化的代码风格,当重用你的代码,就会体会到它的方便。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-05-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 一Li小麦 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 用react的方式来思考
    • 零 任务描述
      • 第一步:将UI分解为组件层次结构
        • 第二步,实现静态的代码
          • 思考:自上而下的数据流
          • 得到以下代码:
          • 静态实现的回顾
        • 第三步,找到最小的(且完整的)的UI状态!
          • 第四步:状态放哪里?
            • 第五步:让数据反向流起来
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档