碎凨

碎凨 查看完整档案

上海编辑  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

碎凨 收藏了文章 · 2019-08-19

一步一步搭建前端监控系统:如何监控资源加载错误?

摘要: 资源加载失败会破坏产品功能以及用户体验....

Fundebug经授权转载,版权归原作者所有。

一步一步搭建前端监控系统系列博客:

怎样定位前端线上问题,一直以来,都是很头疼的问题,因为它发生于用户的一系列操作之后。错误的原因可能源于机型,网络环境,接口请求,复杂的操作行为等等,在我们想要去解决的时候很难复现出来,自然也就无法解决。 当然,这些问题并非不能克服,让我们来一起看看如何去监控并定位线上的问题吧。

背景

市面上的前端监控系统有很多,功能齐全,种类繁多,不管你用或是不用,它都在那里,密密麻麻。往往我需要的功能都在别人家的监控系统里,手动无奈,罢了,怎么才能拥有一个私人定制的前端监控系统呢?做一个自带前端监控系统的前端工程狮是一种怎样的体验呢?

这是搭建前端监控系统的第三章,主要是介绍如何统计静态资源报错,跟着我一步步做,你也能搭建出一个属于自己的前端监控系统。

如果感觉有帮助,或者有兴趣,请关注 or Star Me

请移步线上:前端监控系统

上一章介绍了如何做JS错误监控,还有一种错误是静态资源加载报错,很多时候资源加载报错对前端项目来说是致命的,因为静态资源加载出错了,有可能就会导致前端页面无法渲染,用户就只能对着一个空白屏幕发呆,不知所措。因为突然有一天,我们的线上环境爆出了大量的白屏错误,经过很长时间的排查,终于定位到问题原因:我们使用的CDN路径不知道怎么的,把我们的https协议全部指向了http协议,在安全协议下无法访问非安全协议的资源,导致了大量的白屏。所以我决定增加静态资源监控功能,以应对未来的未知情况。

那么,下边我们就进入正题:

如何监控前端静态资源加载情况?

正常情况下,html页面中主要包含的静态资源有:js文件、css文件、图片文件,这些文件加载失败将直接对页面造成影响甚至瘫痪,所有我们需要把他们统计出来。我不太确定是否需要把所有静态资源文件的加载信息都统计下来,既然加载成功了,页面正常了,应该就没有统计的必要了,所以我们只统计加载出错的情况。

先说一下监控方法:

1)使用script标签的回调方法,在网络上搜索过,看到有人说可以用onerror方法监控报错的情况, 但是经过试验后,发现并没有监控到报错情况,至少在静态资源跨域加载的时候是无法获取的。

2)利用 performance.getEntries()方法,获取到所有加载成功的资源列表,在onload事件中遍历出所有页面资源集合,利用排除法,到所有集合中过滤掉成功的资源列表,即为加载失败的资源。 此方法看似合理,也确实能够排查出加载失败的静态资源,但是检查的时机很难掌握,另外,如果遇到异步加载的js也就歇菜了。

3)添加一个Listener(error)来捕获前端的异常,也是我正在使用的方法,比较靠谱。但是这个方法会监控到很多的error, 所以我们要从中筛选出静态资源加载报错的error, 代码如下:

/**
   * 监控页面静态资源加载报错
   */
  function recordResourceError() {
    // 当浏览器不支持 window.performance.getEntries 的时候,用下边这种方式
    window.addEventListener('error',function(e){
      var typeName = e.target.localName;
      var sourceUrl = "";
      if (typeName === "link") {
        sourceUrl = e.target.href;
      } else if (typeName === "script") {
        sourceUrl = e.target.src;
      }
      var resourceLoadInfo = new ResourceLoadInfo(RESOURCE_LOAD, sourceUrl, typeName, "0");
      resourceLoadInfo.handleLogInfo(RESOURCE_LOAD, resourceLoadInfo);
    }, true);
  }

我们根据报错是的e.target的属性来判断它是link标签,还是script标签。由于目前我关注对前端造成崩溃的错误,所以目前只监控了css,js文件加载错误的情况。

首先,我们要做实时监控和预警,依然关联了7天以前同一时间端的数据,如果某个时间段出现错误量暴增,可以发出警告,及时制止。  

然后,我们还需要知道更多详细的信息,如下图。 不看不知道,一看吓一跳。虽然线上环境并没有给我们报出这么多的问题,但是可以看到,每天还是有很多的静态资源加载报错,有些是很重要的静态资源文件,是必然会导致页面渲染失败的,所以必须要解决。

解决方案

  • 统计出每天的量,列出每天加载报错的变化,点击图表的bar, 可以看到每天的数据变化,以作对比。
  • 分析出静态资源加载出错主要发生在哪些页面上,缩小排查的范围。
  • 分析出影响用户的人数,也许很多错误就发生在一个人身上,减少盲目排查。

静态资源加载监控就完成了, 这里还有一些细节需要处理, 来帮助排查问题, 但是我一时半会儿也想不出来,暂时就说到这里吧。

关于Fundebug

Fundebug专注于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java线上应用实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了20亿+错误事件,付费客户有阳光保险、核桃编程、荔枝FM、掌门1对1、微脉、青团社等众多品牌企业。欢迎大家免费试用!

查看原文

碎凨 赞了文章 · 2019-08-19

一步一步搭建前端监控系统:如何监控资源加载错误?

摘要: 资源加载失败会破坏产品功能以及用户体验....

Fundebug经授权转载,版权归原作者所有。

一步一步搭建前端监控系统系列博客:

怎样定位前端线上问题,一直以来,都是很头疼的问题,因为它发生于用户的一系列操作之后。错误的原因可能源于机型,网络环境,接口请求,复杂的操作行为等等,在我们想要去解决的时候很难复现出来,自然也就无法解决。 当然,这些问题并非不能克服,让我们来一起看看如何去监控并定位线上的问题吧。

背景

市面上的前端监控系统有很多,功能齐全,种类繁多,不管你用或是不用,它都在那里,密密麻麻。往往我需要的功能都在别人家的监控系统里,手动无奈,罢了,怎么才能拥有一个私人定制的前端监控系统呢?做一个自带前端监控系统的前端工程狮是一种怎样的体验呢?

这是搭建前端监控系统的第三章,主要是介绍如何统计静态资源报错,跟着我一步步做,你也能搭建出一个属于自己的前端监控系统。

如果感觉有帮助,或者有兴趣,请关注 or Star Me

请移步线上:前端监控系统

上一章介绍了如何做JS错误监控,还有一种错误是静态资源加载报错,很多时候资源加载报错对前端项目来说是致命的,因为静态资源加载出错了,有可能就会导致前端页面无法渲染,用户就只能对着一个空白屏幕发呆,不知所措。因为突然有一天,我们的线上环境爆出了大量的白屏错误,经过很长时间的排查,终于定位到问题原因:我们使用的CDN路径不知道怎么的,把我们的https协议全部指向了http协议,在安全协议下无法访问非安全协议的资源,导致了大量的白屏。所以我决定增加静态资源监控功能,以应对未来的未知情况。

那么,下边我们就进入正题:

如何监控前端静态资源加载情况?

正常情况下,html页面中主要包含的静态资源有:js文件、css文件、图片文件,这些文件加载失败将直接对页面造成影响甚至瘫痪,所有我们需要把他们统计出来。我不太确定是否需要把所有静态资源文件的加载信息都统计下来,既然加载成功了,页面正常了,应该就没有统计的必要了,所以我们只统计加载出错的情况。

先说一下监控方法:

1)使用script标签的回调方法,在网络上搜索过,看到有人说可以用onerror方法监控报错的情况, 但是经过试验后,发现并没有监控到报错情况,至少在静态资源跨域加载的时候是无法获取的。

2)利用 performance.getEntries()方法,获取到所有加载成功的资源列表,在onload事件中遍历出所有页面资源集合,利用排除法,到所有集合中过滤掉成功的资源列表,即为加载失败的资源。 此方法看似合理,也确实能够排查出加载失败的静态资源,但是检查的时机很难掌握,另外,如果遇到异步加载的js也就歇菜了。

3)添加一个Listener(error)来捕获前端的异常,也是我正在使用的方法,比较靠谱。但是这个方法会监控到很多的error, 所以我们要从中筛选出静态资源加载报错的error, 代码如下:

/**
   * 监控页面静态资源加载报错
   */
  function recordResourceError() {
    // 当浏览器不支持 window.performance.getEntries 的时候,用下边这种方式
    window.addEventListener('error',function(e){
      var typeName = e.target.localName;
      var sourceUrl = "";
      if (typeName === "link") {
        sourceUrl = e.target.href;
      } else if (typeName === "script") {
        sourceUrl = e.target.src;
      }
      var resourceLoadInfo = new ResourceLoadInfo(RESOURCE_LOAD, sourceUrl, typeName, "0");
      resourceLoadInfo.handleLogInfo(RESOURCE_LOAD, resourceLoadInfo);
    }, true);
  }

我们根据报错是的e.target的属性来判断它是link标签,还是script标签。由于目前我关注对前端造成崩溃的错误,所以目前只监控了css,js文件加载错误的情况。

首先,我们要做实时监控和预警,依然关联了7天以前同一时间端的数据,如果某个时间段出现错误量暴增,可以发出警告,及时制止。  

然后,我们还需要知道更多详细的信息,如下图。 不看不知道,一看吓一跳。虽然线上环境并没有给我们报出这么多的问题,但是可以看到,每天还是有很多的静态资源加载报错,有些是很重要的静态资源文件,是必然会导致页面渲染失败的,所以必须要解决。

解决方案

  • 统计出每天的量,列出每天加载报错的变化,点击图表的bar, 可以看到每天的数据变化,以作对比。
  • 分析出静态资源加载出错主要发生在哪些页面上,缩小排查的范围。
  • 分析出影响用户的人数,也许很多错误就发生在一个人身上,减少盲目排查。

静态资源加载监控就完成了, 这里还有一些细节需要处理, 来帮助排查问题, 但是我一时半会儿也想不出来,暂时就说到这里吧。

关于Fundebug

Fundebug专注于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java线上应用实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了20亿+错误事件,付费客户有阳光保险、核桃编程、荔枝FM、掌门1对1、微脉、青团社等众多品牌企业。欢迎大家免费试用!

查看原文

赞 22 收藏 16 评论 0

碎凨 收藏了文章 · 2019-06-13

react 组件传值

整理 react 组件传值 三种方式

1. 父组件向子组件传值(通过props传值)

子组件:

    class Children extends Component{
        constructor(props){
            super(props);
        }
        render(){
            return(
                <div>这是:{this.props.name}</div>  // 这是 父向子
            )
        }
    }

父组件:

    class App extends React.Component{
        render(){
            return(
                <div>
                    <Children name="父向子"/>
                </div>
            )
        }
    }

2. 子组件向父组件传值(回调函数)

子组件

    class Children extends Component{
        constructor(props){
            super(props);
        }
        handerClick(){
            this.props.changeColor('skyblue')  // 执行父组件的changeColor 并传参  必须和父组件中的函数一模一样
        }
        render(){
            return(
                <div>
                    <div>父组件的背景色{this.props.bgcolor}</div>  // 子组件接收父组件传过来的值 bgcolor
                    <button onClick={(e)=>{this.handerClick(e)}}>改变父组件背景</button>  // 子组件执行函数
                </div>
            )
        }
    }
    
父组件

    class Father extends Component{
        constructor(props){
            super(props)
            this.state = {
                bgcolor:'pink'
            }
        }
        bgChange(color){
            this.setState({
                bgcolor:color
            })
        }
        render(props){
            <div style={{background:this.state.bgcolor}}>
                            // 给子组件传递的值  color  
                <Children bgcolor={this.state.bgcolor} changeColor={(color)=>{this.bgChange(color)}} /> 
                                                    // changeColor 子组件的参数=color 当做形参
            </div>
        }
    }
    
   

3. 兄弟组件传值(子传给父,父再传给另一个子)

子组件1
    class Children1 extends Component{
        constructor(props){
            super(props);
        }
        handerClick(){
            this.props.changeChild2Color('skyblue')  
            // 改变兄弟组件的颜色  把changeChild2Color的参数传给父
        }
        render(){
            return(
                <div>
                    <div>children1</div>
                    <button onClick={(e)=>{this.handerClick(e)}}>改变children2背景</button>
                </div>
            )
        }
    }

子组件2
    class Children2 extends Component{
        constructor(props){
            super(props);
        }
        render(){
            return(
                <div style={{background:this.props.bgcolor}}>
                // 从父元素获取自己的背景色
                    <div>children2 背景色 {this.props.bgcolor}</div>
                    // children2 背景色 skyblue
                </div>
            )
        }
    }    
父组件
 class Father extends Component{
    constructor(props){
        super(props)
        this.state = {
            child2bgcolor:'pink'
        }
    }
    onchild2bgChange(color){
        this.setState({
            child2bgcolor:color
        })
    }
    render(props){
        <div>
            <Children1 changeChild2Color={(color)=>{this.onchild2bgChange(color)}} />
            <Children2 bgcolor={this.state.child2bgcolor} />
        </div>
    }
 }
查看原文

碎凨 赞了回答 · 2019-06-06

ant-design tree-select问题

你这问题有点久了,今天用到这块儿了,也发现了问题,后来通过看api解决。
要使用antdesign TreeSelect的搜索功能,至少要知道三个属性,showSearch、filterTreeNode、treeNodeFilterProp。
showSearch:是否启用搜索
treeNodeFilterProp:启用搜索后,输入项过滤对应的 treeNode 属性(比如value、title、key)
filterTreeNode:是否根据输入项进行筛选,默认用 treeNodeFilterProp 的值作为要筛选的 TreeNode 的属性值。

你的问题应该在treeNodeFilterProp这个属性没设置title,这块儿antdesign默认是value。

把你的label换成title

关注 3 回答 2

碎凨 收藏了文章 · 2019-05-29

React中受控与非受控组件

首次发表在个人博客

受控组件

<input
    type="text"
    value={this.state.value}
    onChange={(e) => {
        this.setState({
            value: e.target.value.toUpperCase(),
        });
    }}
/>

<input><select>都要绑定一个change事件;每当表单的状态发生变化,都会被写入组件的state中,这种组件在React中被称为受控组件;在受控组件中,组件渲染出的状态与它的value或者checked prop向对应.react通过这种方式消除了组件的局部状态,是的应用的整个状态可控.react官方同样推荐使用受控表单组件,总结下React受控组件更新state的流程:

  • 1.可以通过初始state中设置表单的默认值;
  • 2.每当表单的值发生变化时,调用onChange事件处理器;
  • 3.事件处理器通过合成事件对象e拿到改变后的状态,并更新应用的state.
  • 4.setState触发视图的重新渲染,完成表单组件值得更新

react中数据是单向流动的.从示例中,我们能看出来表单的数据来源于组件的state,并通过props传入,这也称为单向数据绑定.然后,我们又通过onChange事件处理器将新的表单数据写回到state,完成了双向数据绑定.

非受控组件

  • 如果一个表单组件没有value props(单选按钮和复选按钮对应的是 checked props)时,就可以称为非受控组件;
  • 使用defaultValue和defaultChecked来表示组件的默认状态;
  • 通过 defaultValue和defaultChecked来设置组件的默认值,它仅会被渲染一次,在后续的渲染时并不起作用
import React, { Component } from 'react';

class UnControlled extends Component {
    handleSubmit = (e) => {
        console.log(e);
        e.preventDefault();
        console.log(this.name.value);
    }
    render() {
        return (
            <form onSubmit={this.handleSubmit}>
                <input type="text" ref={i => this.name = i} defaultValue="BeiJing" />
                <button type="submit">Submit</button>
            </form>
        );
    }
}

export default UnControlled;

对比受控组件和非受控组件

将输入的字母转化为大写展示

<input
    type="text"
    value={this.state.value}
    onChange={(e) => {
        this.setState({
            value: e.target.value.toUpperCase(),
        });
    }}
/>

直接展示输入的字母

<input
    type="text"
    defaultValue={this.state.value}
    onChange={(e) => {
        this.setState({
            value: e.target.value.toUpperCase(),
        });
    }}
/>

1.性能上的问题

在受控组件中,每次表单的值发生变化,都会调用一次onChange事件处理器,这确实会带来性能上的的损耗,虽然使用费受控组件不会出现这些问题,但仍然不提倡使用非受控组件,这个问题可以通过Flux/Redux应用架构等方式来达到统一组件状态的目的.

2.是否需要事件绑定

使用受控组件需要为每一个组件绑定一个change事件,并且定义一个事件处理器来同步表单值和组件的状态,这是一个必要条件.当然,某些情况可以使用一个事件处理器来处理多个表单域

import React, { Component } from 'react';

class Controlled extends Component {
    constructor(...args) {
        super(...args);
        this.state = {
            name: 'xingxing',
            age: 18,
        };
    }
    handleChange = (name, e) => {
        const { value } = e.target;
        this.setState({
            [name]: value,
        });
    }
    render() {
        const { name, age } = this.state;
        return (
            <div>
                <input type="text" value={name} onChange={this.handleChange.bind(this, 'name')} />
                <input type="text" value={age} onChange={this.handleChange.bind(this, 'age',)} />
            </div>
        );
    }
}

export default Controlled;

表单组件的几个重要属性

1.状态属性

React的form组件提供了几个重要的属性,用来显示组件的状态

  • value: 类型为text的input组件,textarea组件以及select组件都借助value prop来展示应用的状态
  • checked: 类型为radio或checkbox的组件借助值为boolean类型的selected prop来展示应用的状态
  • selected: 该属性可作用于select组件下面的option上,React并不建议这种方式表示状态.而推荐在select组件上使用value的方式

2.事件属性

当状态属性改变时会触发onChange事件属性.受控组件中的change事件与HTML DOM中提供的input事件更为类似;

查看原文

碎凨 赞了文章 · 2019-05-29

React中受控与非受控组件

首次发表在个人博客

受控组件

<input
    type="text"
    value={this.state.value}
    onChange={(e) => {
        this.setState({
            value: e.target.value.toUpperCase(),
        });
    }}
/>

<input><select>都要绑定一个change事件;每当表单的状态发生变化,都会被写入组件的state中,这种组件在React中被称为受控组件;在受控组件中,组件渲染出的状态与它的value或者checked prop向对应.react通过这种方式消除了组件的局部状态,是的应用的整个状态可控.react官方同样推荐使用受控表单组件,总结下React受控组件更新state的流程:

  • 1.可以通过初始state中设置表单的默认值;
  • 2.每当表单的值发生变化时,调用onChange事件处理器;
  • 3.事件处理器通过合成事件对象e拿到改变后的状态,并更新应用的state.
  • 4.setState触发视图的重新渲染,完成表单组件值得更新

react中数据是单向流动的.从示例中,我们能看出来表单的数据来源于组件的state,并通过props传入,这也称为单向数据绑定.然后,我们又通过onChange事件处理器将新的表单数据写回到state,完成了双向数据绑定.

非受控组件

  • 如果一个表单组件没有value props(单选按钮和复选按钮对应的是 checked props)时,就可以称为非受控组件;
  • 使用defaultValue和defaultChecked来表示组件的默认状态;
  • 通过 defaultValue和defaultChecked来设置组件的默认值,它仅会被渲染一次,在后续的渲染时并不起作用
import React, { Component } from 'react';

class UnControlled extends Component {
    handleSubmit = (e) => {
        console.log(e);
        e.preventDefault();
        console.log(this.name.value);
    }
    render() {
        return (
            <form onSubmit={this.handleSubmit}>
                <input type="text" ref={i => this.name = i} defaultValue="BeiJing" />
                <button type="submit">Submit</button>
            </form>
        );
    }
}

export default UnControlled;

对比受控组件和非受控组件

将输入的字母转化为大写展示

<input
    type="text"
    value={this.state.value}
    onChange={(e) => {
        this.setState({
            value: e.target.value.toUpperCase(),
        });
    }}
/>

直接展示输入的字母

<input
    type="text"
    defaultValue={this.state.value}
    onChange={(e) => {
        this.setState({
            value: e.target.value.toUpperCase(),
        });
    }}
/>

1.性能上的问题

在受控组件中,每次表单的值发生变化,都会调用一次onChange事件处理器,这确实会带来性能上的的损耗,虽然使用费受控组件不会出现这些问题,但仍然不提倡使用非受控组件,这个问题可以通过Flux/Redux应用架构等方式来达到统一组件状态的目的.

2.是否需要事件绑定

使用受控组件需要为每一个组件绑定一个change事件,并且定义一个事件处理器来同步表单值和组件的状态,这是一个必要条件.当然,某些情况可以使用一个事件处理器来处理多个表单域

import React, { Component } from 'react';

class Controlled extends Component {
    constructor(...args) {
        super(...args);
        this.state = {
            name: 'xingxing',
            age: 18,
        };
    }
    handleChange = (name, e) => {
        const { value } = e.target;
        this.setState({
            [name]: value,
        });
    }
    render() {
        const { name, age } = this.state;
        return (
            <div>
                <input type="text" value={name} onChange={this.handleChange.bind(this, 'name')} />
                <input type="text" value={age} onChange={this.handleChange.bind(this, 'age',)} />
            </div>
        );
    }
}

export default Controlled;

表单组件的几个重要属性

1.状态属性

React的form组件提供了几个重要的属性,用来显示组件的状态

  • value: 类型为text的input组件,textarea组件以及select组件都借助value prop来展示应用的状态
  • checked: 类型为radio或checkbox的组件借助值为boolean类型的selected prop来展示应用的状态
  • selected: 该属性可作用于select组件下面的option上,React并不建议这种方式表示状态.而推荐在select组件上使用value的方式

2.事件属性

当状态属性改变时会触发onChange事件属性.受控组件中的change事件与HTML DOM中提供的input事件更为类似;

查看原文

赞 12 收藏 16 评论 0

碎凨 赞了回答 · 2019-05-29

解决这两种写属性验证propTypes的方法是一回事吗

两者是一样的,static 的写法应该只是前者的一种语法糖吧,目前处于提案阶段,babel 支持,具体可以按这里静态属性

// 老写法
class Foo {
}
Foo.prop = 1;

// 新写法
class Foo {
  static prop = 1;
}

关注 3 回答 1

碎凨 收藏了文章 · 2019-05-29

写 React 组件的最佳实践

本文为译文,已获得原作者允许,原文地址:http://scottdomes.com/blog/ou...

当我第一次开始写 React 时,我发现多少个 React 教程,就有多少种写 React 组件方法。虽然如今,框架已经成熟,但是并没有一个 “正确” 写组件的方法。

在 MuseFind 的一年以来,我们的团队写了大量的 React 组件。我们精益求精,不断完善写 React 组件的方法。

本文介绍了,我们团队写 React 组件的最佳实践。
我们希望,无论你是初学者,还是经验丰富的人,这篇文章都会对你有用的。

在开始介绍之前,先说几个点:

  • 我们团队使用 ES6 和 ES7 的语法。

  • 如果不清楚表现组件(presentational components)和容器组件(container components)之间的区别,我们建议先阅读 这篇文章

  • 如果有任何建议,问题或反馈意见,请在评论中通知我们。

基于类的组件

基于类的组件(Class based components)是包含状态和方法的。
我们应该尽可能地使用基于函数的组件(Functional Components
)来代替它们。但是,现在让我们先来讲讲怎么写基于类的组件。

让我们逐行地构建我们的组件。

引入 CSS

import React, { Component } from 'react'
import { observer } from 'mobx-react'

import ExpandableForm from './ExpandableForm'
import './styles/ProfileContainer.css'

我认为最理想的 CSS 应该是 CSS in JavaScript。但是,这仍然是一个新的想法,还没有一个成熟的解决方案出现。
所以,现在我们还是使用将 CSS 文件引入到每个 React 组件中的方法。

我们团队会先引入依赖文件(node_modules 中的文件),然后空一行,再引入本地文件。

初始化状态

import React, { Component } from 'react'
import { observer } from 'mobx-react'

import ExpandableForm from './ExpandableForm'
import './styles/ProfileContainer.css'

export default class ProfileContainer extends Component {
  state = { expanded: false }

可以使用在 constructor 中初始化状态的老方法。
也可以使用 ES7 这种简单的初始化状态的新方法。
更多,请阅读 这里

propTypes and defaultProps

import React, { Component } from 'react'
import { observer } from 'mobx-react'
import { string, object } from 'prop-types'

import ExpandableForm from './ExpandableForm'
import './styles/ProfileContainer.css'

export default class ProfileContainer extends Component {
  state = { expanded: false }
 
  static propTypes = {
    model: object.isRequired,
    title: string
  }
 
  static defaultProps = {
    model: {
      id: 0
    },
    title: 'Your Name'
  }

propTypesdefaultProps 是静态属性(static properties),在组件代码中,最好把它们写在组件靠前的位置。当其他开发人员查看这个组件的代码时,应该立即看到 propTypesdefaultProps,因为它们就好像这个组件的文档一样。(译注:关于组件书写的顺序,参考 这篇文章

如果使用 React 15.3.0 或更高版本,请使用 prop-types 代替 React.PropTypes。使用 prop-types 时,应当将其解构。

所有组件都应该有 propTypes

Methods

import React, { Component } from 'react'
import { observer } from 'mobx-react'
import { string, object } from 'prop-types'

import ExpandableForm from './ExpandableForm'
import './styles/ProfileContainer.css'

export default class ProfileContainer extends Component {
  state = { expanded: false }
 
  static propTypes = {
    model: object.isRequired,
    title: string
  }
 
  static defaultProps = {
    model: {
      id: 0
    },
    title: 'Your Name'
  }
  
  handleSubmit = (e) => {
    e.preventDefault()
    this.props.model.save()
  }
  
  handleNameChange = (e) => {
    this.props.model.changeName(e.target.value)
  }
  
  handleExpand = (e) => {
    e.preventDefault()
    this.setState({ expanded: !this.state.expanded })
  }

使用基于类的组件时,当你将方法传递给组件时,你必须保证方法在调用时具有正确的上下文 this。常见的方法是,通过将 this.handleSubmit.bind(this) 传递给子组件来实现。

我们认为,上述方法更简单,更直接。通过 ES6 箭头功能自动 bind 正确的上下文。

setState 传递一个函数

在上面的例子中,我们这样做:

this.setState({ expanded: !this.state.expanded })

因为 setState 它实际上是异步的。
由于性能原因,所以 React 会批量的更新状态,因此调用 setState 后状态可能不会立即更改。

这意味着在调用 setState 时,不应该依赖当前状态,因为你不能确定该状态是什么!

解决方案是:给 setState 传递函数,而不是一个普通对象。函数的第一个参数是前一个状态。

this.setState(prevState => ({ expanded: !prevState.expanded }))

解构 Props

import React, { Component } from 'react'
import { observer } from 'mobx-react'
import { string, object } from 'prop-types'
import ExpandableForm from './ExpandableForm'
import './styles/ProfileContainer.css'
export default class ProfileContainer extends Component {
  state = { expanded: false }
 
  static propTypes = {
    model: object.isRequired,
    title: string
  }
 
  static defaultProps = {
    model: {
      id: 0
    },
    title: 'Your Name'
  }
handleSubmit = (e) => {
    e.preventDefault()
    this.props.model.save()
  }
  
  handleNameChange = (e) => {
    this.props.model.changeName(e.target.value)
  }
  
  handleExpand = (e) => {
    e.preventDefault()
    this.setState(prevState => ({ expanded: !prevState.expanded }))
  }
  
  render() {
    const {
      model,
      title
    } = this.props
    return ( 
      <ExpandableForm 
        onSubmit={this.handleSubmit} 
        expanded={this.state.expanded} 
        onExpand={this.handleExpand}>
        <div>
          <h1>{title}</h1>
          <input
            type="text"
            value={model.name}
            onChange={this.handleNameChange}
            placeholder="Your Name"/>
        </div>
      </ExpandableForm>
    )
  }
}           

如上,当组件具有多个 props 值时,每个 prop 应当单独占据一行。

装饰器

@observer
export default class ProfileContainer extends Component {

如果使用 mobx,那么应当是用装饰器(decorators)。其本质是将装饰器的组件传递到一个函数。

使用装饰器一种更加灵活和更加可读的方式。
我们团队在使用 mobx 和我们自己的 mobx-models 库时,使用了大量的装饰器。

如果您不想使用装饰器,也可以按照下面的方式做:

class ProfileContainer extends Component {
  // Component code
}
export default observer(ProfileContainer)

闭包

避免传递一个新闭包(Closures)给子组件,像下面这样:

<input
type="text"
value={model.name}
// onChange={(e) => { model.name = e.target.value }}
// ^ 上面是错误的. 使用下面的方法:
onChange={this.handleChange}
placeholder="Your Name"/>

为什么呢?因为每次父组件 render 时,都会创建一个新的函数(译注:通过 (e) => { model.name = e.target.value } 创建的新的函数也叫 闭包)。

如果将这个新函数传给一个 React 组件,无论这个组件的其他 props 有没有真正的改变,都就会导致它重新渲染。

调和(Reconciliation)是 React 中最耗费性能的一部分。因此,要避免传递新闭包的写法,不要让调和更加消耗性能!另外,传递类的方法的之中形式更容易阅读,调试和更改。

下面是我们整个组件:

import React, { Component } from 'react'
import { observer } from 'mobx-react'
import { string, object } from 'prop-types'
// Separate local imports from dependencies
import ExpandableForm from './ExpandableForm'
import './styles/ProfileContainer.css'

// Use decorators if needed
@observer
export default class ProfileContainer extends Component {
  state = { expanded: false }
  // Initialize state here (ES7) or in a constructor method (ES6)
 
  // Declare propTypes as static properties as early as possible
  static propTypes = {
    model: object.isRequired,
    title: string
  }

  // Default props below propTypes
  static defaultProps = {
    model: {
      id: 0
    },
    title: 'Your Name'
  }

  // Use fat arrow functions for methods to preserve context (this will thus be the component instance)
  handleSubmit = (e) => {
    e.preventDefault()
    this.props.model.save()
  }
  
  handleNameChange = (e) => {
    this.props.model.name = e.target.value
  }
  
  handleExpand = (e) => {
    e.preventDefault()
    this.setState(prevState => ({ expanded: !prevState.expanded }))
  }
  
  render() {
    // Destructure props for readability
    const {
      model,
      title
    } = this.props
    return ( 
      <ExpandableForm 
        onSubmit={this.handleSubmit} 
        expanded={this.state.expanded} 
        onExpand={this.handleExpand}>
        // Newline props if there are more than two
        <div>
          <h1>{title}</h1>
          <input
            type="text"
            value={model.name}
            // onChange={(e) => { model.name = e.target.value }}
            // Avoid creating new closures in the render method- use methods like below
            onChange={this.handleNameChange}
            placeholder="Your Name"/>
        </div>
      </ExpandableForm>
    )
  }
}

基于函数的组件

基于函数的组件(Functional Components)是没有状态和方法的。它们是纯粹的、易读的。尽可能的使用它们。

propTypes

import React from 'react'
import { observer } from 'mobx-react'
import { func, bool } from 'prop-types'

import './styles/Form.css'

ExpandableForm.propTypes = {
  onSubmit: func.isRequired,
  expanded: bool
}
// Component declaration

在声明组件之前,给组件定义 propTypes,因为这样它们可以立即被看见。
我们可以这样做,因为 JavaScript 有函数提升(function hoisting)。

解构 Props 和 defaultProps

import React from 'react'
import { observer } from 'mobx-react'
import { func, bool } from 'prop-types'

import './styles/Form.css'

ExpandableForm.propTypes = {
  onSubmit: func.isRequired,
  expanded: bool,
  onExpand: func.isRequired
}

function ExpandableForm(props) {
  const formStyle = props.expanded ? {height: 'auto'} : {height: 0}
  return (
    <form style={formStyle} onSubmit={props.onSubmit}>
      {props.children}
      <button onClick={props.onExpand}>Expand</button>
    </form>
  )
}

我们的组件是一个函数,函数的参数就是组件的 props。我们可以使用解构参数的方式:

import React from 'react'
import { observer } from 'mobx-react'

import { func, bool } from 'prop-types'
import './styles/Form.css'

ExpandableForm.propTypes = {
  onSubmit: func.isRequired,
  expanded: bool,
  onExpand: func.isRequired
}

function ExpandableForm({ onExpand, expanded = false, children, onSubmit }) {
  const formStyle = expanded ? {height: 'auto'} : {height: 0}
  return (
    <form style={formStyle} onSubmit={onSubmit}>
      {children}
      <button onClick={onExpand}>Expand</button>
    </form>
  )
}

注意,我们还可以使用默认参数作为 defaultProps,这种方式可读性更强。
如果 expanded 未定义,则将其设置为false。(这样可以避免类似 ‘Cannot read <property> of undefined’ 之类的错误)

避免使用函数表达式的方式来定义组件,如下:

const ExpandableForm = ({ onExpand, expanded, children }) => {

这看起来非常酷,但是在这里,通过函数表达式定义的函数却是匿名函数。

如果 Bable 没有做相关的命名配置,那么报错时,错误堆栈中不会告诉具体是哪个组件出错了,只会显示 <<anonymous>> 。这使得调试变得非常糟糕。

匿名函数也可能会导致 React 测试库 Jest 出问题。由于这些潜在的隐患,我们推荐使用函数声明,而不是函数表达式。

包裹函数

因为基于函数的组件不能使用修饰器,所以你应当将基于函数的组件当做参数,传给修饰器对应的函数:

import React from 'react'
import { observer } from 'mobx-react'
import { func, bool } from 'prop-types'

import './styles/Form.css'

ExpandableForm.propTypes = {
  onSubmit: func.isRequired,
  expanded: bool,
  onExpand: func.isRequired
}

function ExpandableForm({ onExpand, expanded = false, children, onSubmit }) {
  const formStyle = expanded ? {height: 'auto'} : {height: 0}
  return (
    <form style={formStyle} onSubmit={onSubmit}>
      {children}
      <button onClick={onExpand}>Expand</button>
    </form>
  )
}

export default observer(ExpandableForm)

全部的代码如下:

import React from 'react'
import { observer } from 'mobx-react'
import { func, bool } from 'prop-types'
// Separate local imports from dependencies
import './styles/Form.css'

// Declare propTypes here, before the component (taking advantage of JS function hoisting)
// You want these to be as visible as possible
ExpandableForm.propTypes = {
  onSubmit: func.isRequired,
  expanded: bool,
  onExpand: func.isRequired
}

// Destructure props like so, and use default arguments as a way of setting defaultProps
function ExpandableForm({ onExpand, expanded = false, children, onSubmit }) {
  const formStyle = expanded ? { height: 'auto' } : { height: 0 }
  return (
    <form style={formStyle} onSubmit={onSubmit}>
      {children}
      <button onClick={onExpand}>Expand</button>
    </form>
  )
}

// Wrap the component instead of decorating it
export default observer(ExpandableForm)

JSX 中的条件表达式

很可能你会做很多条件渲染。这是你想避免的:

图片描述

不,三目嵌套不是一个好主意。

有一些库解决了这个问题(JSX-Control Statementments),但是为了引入另一个依赖库,我们使用复杂的条件表达式,解决了这个问题:

图片描述

使用大括号包裹一个立即执行函数(IIFE),然后把你的 if 语句放在里面,返回你想要渲染的任何东西。
请注意,像这样的 IIFE 可能会导致一些性能消耗,但在大多数情况下,可读性更加重要。

更新:许多评论者建议将此逻辑提取到子组件,由这些子组件返回的不同 button。这是对的,尽可能地拆分组件。

另外,当你有布尔判断渲染元素时,不应该这样做:

{
  isTrue
   ? <p>True!</p>
   : <none/>
}

应该使用短路运算:

{
  isTrue && 
    <p>True!</p>
}
查看原文

碎凨 赞了文章 · 2019-05-29

写 React 组件的最佳实践

本文为译文,已获得原作者允许,原文地址:http://scottdomes.com/blog/ou...

当我第一次开始写 React 时,我发现多少个 React 教程,就有多少种写 React 组件方法。虽然如今,框架已经成熟,但是并没有一个 “正确” 写组件的方法。

在 MuseFind 的一年以来,我们的团队写了大量的 React 组件。我们精益求精,不断完善写 React 组件的方法。

本文介绍了,我们团队写 React 组件的最佳实践。
我们希望,无论你是初学者,还是经验丰富的人,这篇文章都会对你有用的。

在开始介绍之前,先说几个点:

  • 我们团队使用 ES6 和 ES7 的语法。

  • 如果不清楚表现组件(presentational components)和容器组件(container components)之间的区别,我们建议先阅读 这篇文章

  • 如果有任何建议,问题或反馈意见,请在评论中通知我们。

基于类的组件

基于类的组件(Class based components)是包含状态和方法的。
我们应该尽可能地使用基于函数的组件(Functional Components
)来代替它们。但是,现在让我们先来讲讲怎么写基于类的组件。

让我们逐行地构建我们的组件。

引入 CSS

import React, { Component } from 'react'
import { observer } from 'mobx-react'

import ExpandableForm from './ExpandableForm'
import './styles/ProfileContainer.css'

我认为最理想的 CSS 应该是 CSS in JavaScript。但是,这仍然是一个新的想法,还没有一个成熟的解决方案出现。
所以,现在我们还是使用将 CSS 文件引入到每个 React 组件中的方法。

我们团队会先引入依赖文件(node_modules 中的文件),然后空一行,再引入本地文件。

初始化状态

import React, { Component } from 'react'
import { observer } from 'mobx-react'

import ExpandableForm from './ExpandableForm'
import './styles/ProfileContainer.css'

export default class ProfileContainer extends Component {
  state = { expanded: false }

可以使用在 constructor 中初始化状态的老方法。
也可以使用 ES7 这种简单的初始化状态的新方法。
更多,请阅读 这里

propTypes and defaultProps

import React, { Component } from 'react'
import { observer } from 'mobx-react'
import { string, object } from 'prop-types'

import ExpandableForm from './ExpandableForm'
import './styles/ProfileContainer.css'

export default class ProfileContainer extends Component {
  state = { expanded: false }
 
  static propTypes = {
    model: object.isRequired,
    title: string
  }
 
  static defaultProps = {
    model: {
      id: 0
    },
    title: 'Your Name'
  }

propTypesdefaultProps 是静态属性(static properties),在组件代码中,最好把它们写在组件靠前的位置。当其他开发人员查看这个组件的代码时,应该立即看到 propTypesdefaultProps,因为它们就好像这个组件的文档一样。(译注:关于组件书写的顺序,参考 这篇文章

如果使用 React 15.3.0 或更高版本,请使用 prop-types 代替 React.PropTypes。使用 prop-types 时,应当将其解构。

所有组件都应该有 propTypes

Methods

import React, { Component } from 'react'
import { observer } from 'mobx-react'
import { string, object } from 'prop-types'

import ExpandableForm from './ExpandableForm'
import './styles/ProfileContainer.css'

export default class ProfileContainer extends Component {
  state = { expanded: false }
 
  static propTypes = {
    model: object.isRequired,
    title: string
  }
 
  static defaultProps = {
    model: {
      id: 0
    },
    title: 'Your Name'
  }
  
  handleSubmit = (e) => {
    e.preventDefault()
    this.props.model.save()
  }
  
  handleNameChange = (e) => {
    this.props.model.changeName(e.target.value)
  }
  
  handleExpand = (e) => {
    e.preventDefault()
    this.setState({ expanded: !this.state.expanded })
  }

使用基于类的组件时,当你将方法传递给组件时,你必须保证方法在调用时具有正确的上下文 this。常见的方法是,通过将 this.handleSubmit.bind(this) 传递给子组件来实现。

我们认为,上述方法更简单,更直接。通过 ES6 箭头功能自动 bind 正确的上下文。

setState 传递一个函数

在上面的例子中,我们这样做:

this.setState({ expanded: !this.state.expanded })

因为 setState 它实际上是异步的。
由于性能原因,所以 React 会批量的更新状态,因此调用 setState 后状态可能不会立即更改。

这意味着在调用 setState 时,不应该依赖当前状态,因为你不能确定该状态是什么!

解决方案是:给 setState 传递函数,而不是一个普通对象。函数的第一个参数是前一个状态。

this.setState(prevState => ({ expanded: !prevState.expanded }))

解构 Props

import React, { Component } from 'react'
import { observer } from 'mobx-react'
import { string, object } from 'prop-types'
import ExpandableForm from './ExpandableForm'
import './styles/ProfileContainer.css'
export default class ProfileContainer extends Component {
  state = { expanded: false }
 
  static propTypes = {
    model: object.isRequired,
    title: string
  }
 
  static defaultProps = {
    model: {
      id: 0
    },
    title: 'Your Name'
  }
handleSubmit = (e) => {
    e.preventDefault()
    this.props.model.save()
  }
  
  handleNameChange = (e) => {
    this.props.model.changeName(e.target.value)
  }
  
  handleExpand = (e) => {
    e.preventDefault()
    this.setState(prevState => ({ expanded: !prevState.expanded }))
  }
  
  render() {
    const {
      model,
      title
    } = this.props
    return ( 
      <ExpandableForm 
        onSubmit={this.handleSubmit} 
        expanded={this.state.expanded} 
        onExpand={this.handleExpand}>
        <div>
          <h1>{title}</h1>
          <input
            type="text"
            value={model.name}
            onChange={this.handleNameChange}
            placeholder="Your Name"/>
        </div>
      </ExpandableForm>
    )
  }
}           

如上,当组件具有多个 props 值时,每个 prop 应当单独占据一行。

装饰器

@observer
export default class ProfileContainer extends Component {

如果使用 mobx,那么应当是用装饰器(decorators)。其本质是将装饰器的组件传递到一个函数。

使用装饰器一种更加灵活和更加可读的方式。
我们团队在使用 mobx 和我们自己的 mobx-models 库时,使用了大量的装饰器。

如果您不想使用装饰器,也可以按照下面的方式做:

class ProfileContainer extends Component {
  // Component code
}
export default observer(ProfileContainer)

闭包

避免传递一个新闭包(Closures)给子组件,像下面这样:

<input
type="text"
value={model.name}
// onChange={(e) => { model.name = e.target.value }}
// ^ 上面是错误的. 使用下面的方法:
onChange={this.handleChange}
placeholder="Your Name"/>

为什么呢?因为每次父组件 render 时,都会创建一个新的函数(译注:通过 (e) => { model.name = e.target.value } 创建的新的函数也叫 闭包)。

如果将这个新函数传给一个 React 组件,无论这个组件的其他 props 有没有真正的改变,都就会导致它重新渲染。

调和(Reconciliation)是 React 中最耗费性能的一部分。因此,要避免传递新闭包的写法,不要让调和更加消耗性能!另外,传递类的方法的之中形式更容易阅读,调试和更改。

下面是我们整个组件:

import React, { Component } from 'react'
import { observer } from 'mobx-react'
import { string, object } from 'prop-types'
// Separate local imports from dependencies
import ExpandableForm from './ExpandableForm'
import './styles/ProfileContainer.css'

// Use decorators if needed
@observer
export default class ProfileContainer extends Component {
  state = { expanded: false }
  // Initialize state here (ES7) or in a constructor method (ES6)
 
  // Declare propTypes as static properties as early as possible
  static propTypes = {
    model: object.isRequired,
    title: string
  }

  // Default props below propTypes
  static defaultProps = {
    model: {
      id: 0
    },
    title: 'Your Name'
  }

  // Use fat arrow functions for methods to preserve context (this will thus be the component instance)
  handleSubmit = (e) => {
    e.preventDefault()
    this.props.model.save()
  }
  
  handleNameChange = (e) => {
    this.props.model.name = e.target.value
  }
  
  handleExpand = (e) => {
    e.preventDefault()
    this.setState(prevState => ({ expanded: !prevState.expanded }))
  }
  
  render() {
    // Destructure props for readability
    const {
      model,
      title
    } = this.props
    return ( 
      <ExpandableForm 
        onSubmit={this.handleSubmit} 
        expanded={this.state.expanded} 
        onExpand={this.handleExpand}>
        // Newline props if there are more than two
        <div>
          <h1>{title}</h1>
          <input
            type="text"
            value={model.name}
            // onChange={(e) => { model.name = e.target.value }}
            // Avoid creating new closures in the render method- use methods like below
            onChange={this.handleNameChange}
            placeholder="Your Name"/>
        </div>
      </ExpandableForm>
    )
  }
}

基于函数的组件

基于函数的组件(Functional Components)是没有状态和方法的。它们是纯粹的、易读的。尽可能的使用它们。

propTypes

import React from 'react'
import { observer } from 'mobx-react'
import { func, bool } from 'prop-types'

import './styles/Form.css'

ExpandableForm.propTypes = {
  onSubmit: func.isRequired,
  expanded: bool
}
// Component declaration

在声明组件之前,给组件定义 propTypes,因为这样它们可以立即被看见。
我们可以这样做,因为 JavaScript 有函数提升(function hoisting)。

解构 Props 和 defaultProps

import React from 'react'
import { observer } from 'mobx-react'
import { func, bool } from 'prop-types'

import './styles/Form.css'

ExpandableForm.propTypes = {
  onSubmit: func.isRequired,
  expanded: bool,
  onExpand: func.isRequired
}

function ExpandableForm(props) {
  const formStyle = props.expanded ? {height: 'auto'} : {height: 0}
  return (
    <form style={formStyle} onSubmit={props.onSubmit}>
      {props.children}
      <button onClick={props.onExpand}>Expand</button>
    </form>
  )
}

我们的组件是一个函数,函数的参数就是组件的 props。我们可以使用解构参数的方式:

import React from 'react'
import { observer } from 'mobx-react'

import { func, bool } from 'prop-types'
import './styles/Form.css'

ExpandableForm.propTypes = {
  onSubmit: func.isRequired,
  expanded: bool,
  onExpand: func.isRequired
}

function ExpandableForm({ onExpand, expanded = false, children, onSubmit }) {
  const formStyle = expanded ? {height: 'auto'} : {height: 0}
  return (
    <form style={formStyle} onSubmit={onSubmit}>
      {children}
      <button onClick={onExpand}>Expand</button>
    </form>
  )
}

注意,我们还可以使用默认参数作为 defaultProps,这种方式可读性更强。
如果 expanded 未定义,则将其设置为false。(这样可以避免类似 ‘Cannot read <property> of undefined’ 之类的错误)

避免使用函数表达式的方式来定义组件,如下:

const ExpandableForm = ({ onExpand, expanded, children }) => {

这看起来非常酷,但是在这里,通过函数表达式定义的函数却是匿名函数。

如果 Bable 没有做相关的命名配置,那么报错时,错误堆栈中不会告诉具体是哪个组件出错了,只会显示 <<anonymous>> 。这使得调试变得非常糟糕。

匿名函数也可能会导致 React 测试库 Jest 出问题。由于这些潜在的隐患,我们推荐使用函数声明,而不是函数表达式。

包裹函数

因为基于函数的组件不能使用修饰器,所以你应当将基于函数的组件当做参数,传给修饰器对应的函数:

import React from 'react'
import { observer } from 'mobx-react'
import { func, bool } from 'prop-types'

import './styles/Form.css'

ExpandableForm.propTypes = {
  onSubmit: func.isRequired,
  expanded: bool,
  onExpand: func.isRequired
}

function ExpandableForm({ onExpand, expanded = false, children, onSubmit }) {
  const formStyle = expanded ? {height: 'auto'} : {height: 0}
  return (
    <form style={formStyle} onSubmit={onSubmit}>
      {children}
      <button onClick={onExpand}>Expand</button>
    </form>
  )
}

export default observer(ExpandableForm)

全部的代码如下:

import React from 'react'
import { observer } from 'mobx-react'
import { func, bool } from 'prop-types'
// Separate local imports from dependencies
import './styles/Form.css'

// Declare propTypes here, before the component (taking advantage of JS function hoisting)
// You want these to be as visible as possible
ExpandableForm.propTypes = {
  onSubmit: func.isRequired,
  expanded: bool,
  onExpand: func.isRequired
}

// Destructure props like so, and use default arguments as a way of setting defaultProps
function ExpandableForm({ onExpand, expanded = false, children, onSubmit }) {
  const formStyle = expanded ? { height: 'auto' } : { height: 0 }
  return (
    <form style={formStyle} onSubmit={onSubmit}>
      {children}
      <button onClick={onExpand}>Expand</button>
    </form>
  )
}

// Wrap the component instead of decorating it
export default observer(ExpandableForm)

JSX 中的条件表达式

很可能你会做很多条件渲染。这是你想避免的:

图片描述

不,三目嵌套不是一个好主意。

有一些库解决了这个问题(JSX-Control Statementments),但是为了引入另一个依赖库,我们使用复杂的条件表达式,解决了这个问题:

图片描述

使用大括号包裹一个立即执行函数(IIFE),然后把你的 if 语句放在里面,返回你想要渲染的任何东西。
请注意,像这样的 IIFE 可能会导致一些性能消耗,但在大多数情况下,可读性更加重要。

更新:许多评论者建议将此逻辑提取到子组件,由这些子组件返回的不同 button。这是对的,尽可能地拆分组件。

另外,当你有布尔判断渲染元素时,不应该这样做:

{
  isTrue
   ? <p>True!</p>
   : <none/>
}

应该使用短路运算:

{
  isTrue && 
    <p>True!</p>
}
查看原文

赞 13 收藏 27 评论 12

碎凨 关注了问题 · 2019-05-24

微信小程序怎么把临时路径图片转二进制流

微信小程序怎么把wx.chooseImage生成的临时路径图片转换成二进制内容

关注 2 回答 0

认证与成就

  • 获得 0 次点赞
  • 获得 4 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 4 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2017-08-23
个人主页被 458 人浏览