React使用总结

为何使用react

jquery 修改一个功能时:

通过DOM上的各种属性,找到这个节点,然后直接修改其中的值,很难保证查找的DOM节点没有遗漏。需要修改多处的值,难以保证自己的修改完整、没有副作用。

DOM树的状态和js中数据的状态是分离的,修改DOM的目的就是维护DOM树和js状态的一致

项目复杂之后,维护成本高:代码运行的某一时刻中,虽然你目标的DOM状态是确定的,但当前DOM树的状态却可能是多种多样的(不确定的)。理论上要把DOM树改变成目标状态的话, 你的代码就需要考虑当前DOM树各种可能的状态。不仅如此,你的代码还需要包含改变DOM树状态 的一步步的操作。

React响应式的设计方式

UI=f(data)

可以通过改变应用中的数据来改变React应用的状态,React应用中的每一部分都使用props(外部传入)和states(本身)两个属性来储存状态。state属性产生于ReactElement的内部,可传递到子ReactElement中作为props。

PS:React与传统MVC客户端框架有着大大的不同,因为react框架只是利用javascript去合成复杂的用户界面,所有与用户之间的交互都在可声明的组件中,并没有直接可以看到其他框架中很常见的数据绑定。

简介Virtual DOM

上文已经提到React能够实现DOM树和真实数据状态的一致,这主要归功于其Virtual DOM的实现,使其不用使用类似MVVM的双向绑定这种方式也能保持View和Model状态的一致性。

virtual DOM的基础是virtual Node,一般是树状结构,是一个轻量级的js对象。
其用于创建真实节点的数据包括:元素类型、元素属性、元素的子节点。
首先更新virtual Dom树,对比前后两颗树的区别,将差异部分映射到实际DOM。

//得到一个virtual Node
var tree=h('div',[
    h('span','hello'),
    h('span','world')
])
//通过virtual-dom提供的createElement创建真实节点
var rootNode=createElement(tree);
//追加到页面
document.body.appendChild(rootNode);

更新

//状态变更时,重新构造一颗用js表示的DOM树
var new_tree=render(new_state);
//使用高效的diff算法,比较两颗树,记录差异
var patches=diff(tree,new_tree);
//将差异应用到真实DOM
rootNode=patch(rootNode,patches);

诺,其主要思路就如上了,如有兴趣,可以进一步欣赏其源码,网上简易版的实现也有很多。

PS:标准的diff算法(如版本控制工具Git的diff)时间复杂度为O(n^3),这种代价对于在前端运行来说来高了。react结合Web界面的特点做出了两个简单的假设,将其diff算法的时间复杂度降低到O(n)。

  • 两个相同组件产生类似的DOM结构,不同的组件产生不同的DOM结构;
  • 对于同一层次的一组子节点,它们可以通过唯一的id进行区分。

两个相同组件产生类似的DOM结构,不同的组件产生不同的DOM结构;
对于同一层次的一组子节点,它们可以通过唯一的id进行区分。

一切皆组件

不仅仅是良好的组件封装与使用的易用性,更重要的是除了要渲染出来的UI也可以当做组件。比如只是定时向服务端发送当前页面数据的请求,除此之外没有任何渲染逻辑。

import {Component} from 'react';

class postMessage extends Component{
  constructor(props){
    super(props)
  }
  componentDidMount(){
    this.timer=setInterval(function(){
      fetch('/api/fetchData')
    },1000)
  }
  componentWillUnmount(){
    clearInterval(this.timer)
  }
  render(){
    return null;
  }
}
export default postMessage;

生命周期

组件加载(初始化):

  • constructor()对state初始化工作

    constructor(props){
     super(props);
     this.state={}
    }
    
  • componentWillMount()
    render方法之前,只执行一次

  • render()
    渲染组件挂载到网页

  • componentDidMount()
    组件加载完之后立即执行,组件已经生成DOM结构,并且已经被挂载到了网页上,可以保证数据的加载。【常在该方法中加载外部数据】

    在这儿执行setTimeout或者AJAX请求()。如果是异步获取数据,可以在componentWillUnmount中取消发送请求。
    调用setState方法会重新渲染

    为何在这儿获取数据?

    constructor中会妨碍组件的渲染;
    componentWillMount中触发setState不会重新渲染;

组件更新

  • componentWillReceiveProps(nextProps)

    在接收到新的props时才被执行

  • shouldComponentUpdate(nextProps,nextState)

    【如果需要考虑性能,可以使用shouldComponentUpdate来提升速度】

    是拯救性能的紧急出口

  • componentWillUpdate

  • render

  • componentDidUpdate

    【在组件完成更新后被使用,比如清除notification文字等操作】

  • componentWillUnmount

    【执行一些必要的清理任务,比如清除setTimeout或者在componentDidMount
    中创建的DOM元素】


高阶组件

const HoC = (WrappedComponent) => {
    const WrappingComponent = (props) => (
        <div className="foo">
            <WrappedCompoent {...props} />
        </div>
    );
    return WrappingComponent;
};

高阶组件中可以传入任意多个想要传入的组件。

const HoC = (WrappedComponent, LoginView) => {
    const WrappingComponent = () => {
         const {user} = this.props;  
         if (user) {
            return <WrappedComponent {...this.props} />
         } else {
            return <LoginView {...this.props} />
         }
    };
    return WrappingComponent;
};

React中的组件通信

  • react 中提供的 props 与回调函数。

    解决父子组件之间的通信。这是React官方提供的方式,父组件通过props向子组件传递数据,子组件向父组件可以使用此前通过props传递的函数以回调形式通信。

  • 以事件的形式,比如观察者模式

    可用来解决兄弟组件以及远亲组件之间的通信。其中兄弟组件,也可以通过借助共同的父组件按照父子组件的通信方式来通信。观察者模式对于血缘关系远的组件保持通信,并有效解耦。但是会使得数据流向不明朗,同时给debug带来困难。

  • 基于redux

    如果使用了Redux的话,就可以利用其只有一个全局状态树的特性来进行信息的传递。Redux内部也是使用了观察者模式,对组件间的解耦提供了很多帮助。

    //创建一个store
    function reducer(state={},action){}
    let store=createStore(reducer);
    //在组件A中发布
    store.dispatch({
        type:'child_1',
        data:'hello'
    })
    //在要通信的组件B中订阅
    constructor(props){
        super(props);
        state={
            msg:"aaa"
        }
    }
    compoenentDidMount(){
        store.subscribe(()=>{
            let new_state=store.getState();
            if(new_state.type==='child_1'){
                this.setState({
                    msg:new_state.data
                })
            }
        })
    }
    
    

react应用性能

给应用性能带来影响的来源:

  • 组件中不更新DOM的冗余操作
  • DOM比对那些无需更新的叶子节点

意味着每次更新顶级的Props,整个应用的每一个组件都会渲染,导致性能问题,但是每个组件都有shouldComponentUpdate(),当组件需要更新时返回true,不必更新时返回false。

为了获得理想的更新,尽可能在shouldComponentUpdate中返回false

  • 加速检查

    不做深度检查,只要对象的值发生了改变,就改变对象的引用。在reducer中使用延伸操作符...也可以加快数据对比。

    //在这个reducer中我们改变一个item 的descriptionexport 
    default (state, action) {
        if (action.type === 'ITEM_DESCRIPTION_UPDATE') {
            const { itemId, description } = action
            const items = state.items.map(item => {
                //跟这个action无关的item,不做修改直接返回
                if (item.id != itemId) {
                    return item; }
                //跟这个action相关的item,但是只更改description的值
                return {...item, description };
            });
            return {...state, items }
        }
        return state;
    }
    

在shouldComponentUpdate方法中只做引用检查

shouldComponentUpdate(nextProps) {
  return isObjectEqual(this.props, nextProps); }

isObjectEqual实现的实例:

const isArrayEqual = (array1 = [], array2 = []) => {
  if (array1 === array2) {
      return true;
  }
  return array1.length === array2.length && 
    array1.every((item, index) => item === array2[index]);
}
const isObjectEqual = (obj1, obj2) => {
  if (!isObject(obj1) || !isObject(obj2)) {
      return false;
  }
  //引用是否相同 
  if (obj1 === obj2) {
      return true;
  }
  //包含的键名是否一致 
  const item1Keys = Object.keys(obj1).sort();
  const item2Keys = Object.keys(obj2).sort();
  if (!isArrayEqual(item1Keys, item2Keys)) {
      return false;
  }
  //每个键包含的值是否一致 
  return item2Keys.every((key) => {
          const value = obj1[key];
          const nextValue = obj2[key];
          if (value === nextValue) {
              return true; }
          //如果是数组,再检查一个层级的深度 
          return Array.isArray(value) &&
             Array.isArray(nextValue) && 
             isArrayEqual(value, nextValue);
      }
  })
}

  • 简化检查

    复杂的数据结构会导致众多条件的判断、嵌套层级深。
    解决办法:数据扁平化
    从而可以使用简单的引用检查

使用immutable.js

除了针对shouldComponentUpdate进行修改,使用不可变数据也是应用提升的方法。

js 中的对象一般是可变的,因为使用了引用赋值,新的对象简单的使用了原始对象,改变新的对象也将影响到原始对象,如:

foo={a:1};bar=foo;bar.a=2

则foo.a也成了2,虽然这样可以节约内存,但是应用复杂之后,带来隐患。一般的解决方法是使用浅拷贝和深拷贝(针对Object /Array复杂对象)避免被修改,但造成了CPU和内存的浪费。

  • 浅拷贝:只复制一层对象的属性,而js存储对象都是存地址,所以浅拷贝的对象指向同一块内存地址,会同时修改。
  • 深拷贝:递归复制所有层级到新对象。
//方法1:通过递归
function deepCopy(o, c) {
   var c = c || {}
   for (var i in o) {
       if (typeof o[i] === 'object') {
           if (o[i].constructor === Array) {
               c[i] = []
           } else {
               c[i] = {}
           }
           deepCopy(c[i], o[i]);
       } else {
           c[i] = o[i]
       }
       return c;
   }
}
//方法2:通过json解析
var result = JSON.parse(JSON.stringify(test))

Immutable Data 就是一旦创建,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象。Immutable 实现的原理是 Persistent Data Structure(持久化数据结构),也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变。

同时为了避免 deepCopy 把所有节点都复制一遍带来的性能损耗,Immutable 使用了 Structural Sharing(结构共享),即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。


PropTypes

如果props是复杂类型,使用shape 和arrayOf()

{ text:'hello world', numbers:[1,2,3]}

检测内部对象:

propTypes: {
    myObject: React.PropTypes.shape({
        text: React.PropTypes.string,
        numbers: React.PropTypes.arrayOf(React.PropTypes.number)
    })
}

其它

计算条件和判断,交给render方法

能用三元运算符,不用if

动态处理classNames:使用工具classnames拼接

var classNames = reuqire('classnames');
render() {
        var btnClass = classNames({ 
          'btn': true, 
          'btn-pressed': this.state.isPressed 
        }) 
        return { < span className = { btnClass } > button< /span> }}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,907评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,546评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,705评论 0 238
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,624评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,940评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,371评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,672评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,396评论 0 195
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,069评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,350评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,876评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,243评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,847评论 3 231
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,004评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,755评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,378评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,266评论 2 259

推荐阅读更多精彩内容