第二章 漫谈React

  • 2.1 事件系统

    Virtual DOM在内存中是以对象形式存在的,所以在对象上添加事件是非常简单的。React基于Virtual DOM实现了一个SyntheticEvent(合成时间)层,我们所定义的时间都会接接受一个SyntheticEvent对象的实例,它完全符合W3C标准,不会存在任何IE标准兼容性问题。并且与原生浏览器事件一样拥有同样的接口,同样我们可以使用stopPropagation()he preventDefault()来终端它。     如果要访问原生事件对象,可以使用nativeEvent属性。

  • 合成事件的绑定方式

    <button onClick={this.handleClick}>Test</button> 我们需要用驼峰式来书写事件属性名(onClick)

  • 合成事件的实现机制

1、事件委派 React并不会把事件处理函数直接绑定到真实的节点上,而是把所有的事件都绑定到结构的最外层,使用一个统一的监听器,当事件发生时,首先被这个统一的时间监听器处理,然后再映射到真正的事件处理函数并调用。
2、自动绑定
bind方法:这个方法可以帮助我们处理事件处理器内的this,并传递参数,参考:

import React,{Component} from 'react';

class App extends Component {
    handleClick(e,arg){
        console.log(e,arg);
        //构造器内声明 this.handleClick = this.handleClick.bind(this);
    }
    render() {
        return <button onClick={this.handleClick.bind(this,'test')}>Test</button>
        //双冒号绑定 不传参 <button onClick={::this.handleClick}>Test</button>
        //箭头函数绑定 <button onClick={() => this.handleClick}>Text</button>
    }
}
复制代码
  • 在React中使用原生事件

    componentDidMount会在组件已经安装并且在浏览器中存在真是的DOM后调用,此时我们就可以完成事件绑定,比如:

componentDidMount() {
    this.refs.button.addEventListener('click' ,e => {
        console.log(e)
    })
}
componentWillUnMount() {
    this.refs.button.removeEventListener('click')
}
render() {
    return <button ref="button">Test</button>
}
复制代码
  • 合成事件与原生事件混用

不要将合成事件和原生事件混用,可以通过e.target来避免 p53
对于无法使用React合成事件的场景,我们还需要使用原生事件来完成。

  • 对比React合成事件与javascript原生事件

React合成事件中,只需要用e.preventDefault()就可以阻止事件传播。
事件对象:原生DOM事件对象在W3C标准和IE标准下存在着差异。在低版本IE浏览器中,使用window.event来获取事件对象。而React的合成事件则不存在兼容性问题。

  • 2.2.2受控组件

每当表单状态发生变化时,都会被写入到组件的state中,这种在React中叫做受控组件。
React受控组件更新state流程:
1、可以通过在初始state中设置表单的默认值;
2、每当表单的值发生变化时,调用onChange事件处理器;
3、事件处理器通过合成对象e拿到改变后的状态,并更新应用state;
4、setState重新渲染

  • 2.2.3非受控组件

在React中非受控组件是一种反模式,它的值不受组件自身的state或props控制。需要添加ref来访问底层dom

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

最大区别,非受控组件的状态并不会受到应用状态的控制,而受控组件的值来自组件的state
受控组件使用一个事件处理器来处理多个表单域:

handleChange(name,e) {
    this.setState({
        [name]: e.target.value
    })
}
复制代码
  • 2.25表单的几个重要属性

1、状态属性

value:类型为text的input组件、textarea以及select组件都借助value prop来展示应用状态 <input type='text' value={this.state.value}/>
checked:类型为radio或checkbox的组件借助值为boolean类型的selected prop来展示应用的状态 <input type='checkbox' value='Cappuccino' checked={coffee.indexOf('Cappuccino') !== -1} selected:该属性用于select组件下面的option上,React并不建议使用这种方式,而推荐select组件上使用value

2、事件属性
状态属性变化时,都会触发onChange事件

  • 2.3样式处理

1、设置组件的className
2、设置行内样式对象

const style = {
    color:'white',
    fontSize: 16, // react会自动添加px,但是像lineHeight这种还支持数字直接作为值的就不会添加
}
const component = <Component style={style} />
复制代码
  • CSS Modules

css模块化的解决方案主要有两种:Inline Style和CSS Modules
遇到的问题?
全局污染、命名混乱、依赖管理不彻底、无法共享变量、代码压缩不彻底

在webpack中开启css modules,css?modules&localIdentName=[name]__[local]-[hash:base64:5]//加上modules即为启用

3、使用:local(局部样式):global(全局样式) composes合并样式 4、实现css和js变量共享

// config.scss
$primary-color: #f40;
:export {
    primaryColor:$primary-color
}
//app.js
import style from 'config.scss'
console.log(style.primaryColor); //输出#f40
复制代码

5、CSS Modules结合React实践
在js中的className对应css的class即可 6、如果不想频繁的输入styles.** 可以使用react-css-modulesexport default CSSModules(***,styles),类似于react-redux的connect方法

  • 2.4组件之间的通信

1、父组件向子组件通信(通过props向子组件传递需要的信息)
2、子组件向父组件通行
利用回调函数、利用自定义事件机制。
3、跨组件通信
context,例如:

class ListItem extends Component{
    static contextTypes = {
        color: PropTypes.string
    }
    render() {
        return (
            <li style={{backgroundColor: this.context.color}}>hello world</li>
        )
    }
}

class List extends Component {
    static childContextTypes = {
        color:PropTypes.string
    }
    
    getChildContext() {
        return {
            color: 'red'
        }
    }
}
复制代码

4、没有嵌套关系的组件通信
使用EventEmitter,在组件通过以下例子使用:

//event.js
import {EventEmitter} from 'events';  export default new EventEmitter();
//子组件
import emitter from './event'
onItemChange(entry) {
    ...;
    emitter.emitEvent('itemChange',entry);
}

//父组件
componentDidMount() {
    this.itemChange = emitter.addListener('itemChange', (data) => {
        console.log(data)
    }
    )
}
复制代码

5、组件间的抽象(mixin和高阶组件)
封装mixin方法,js对象

const mixin = (oldObj,mixins) => {
    const newObj = oldObj;
    newObj.prototype = Object.create(oldObj.prototype);
    for(let props in mixins) {
        if(mixins.hasOwnProperty(props)) {
            newObj.prototype[props] = mixins[props];
        }
    }
}
复制代码

mixin带来的问题
1、破坏了原有组件的封装
2、命名冲突
3、增加复杂性

  • 2.5.2高阶组件

高阶组件用通俗的语言来说就是,当React组件被包裹时,高阶组件会返回一个增强的React组件。它的实现方法有以下两种: 1、属性代理(props proxy)

import React,{Component} from 'React';
const myContainer = (WrappedComponent) => (
    class extends Component{
        render() {
            return <WrappedComponent {...this.props}/>
        }
    }
)

//调用时
export default myContainer(MyComponent);
复制代码

我们可以控制props,包括读取、增加、编辑或者移除,下面的例子调用高阶函数我们都可以获取一个text的props值

const myContainer = (WrappedComponent) => (
    class extends Component{
        render() {
            const newProps = {
                text: 'hello world'
            };
            return <WrappedComponent {...this.props} {...newProps}/>
        }
    }
)
复制代码

通过refs引用,那么当WrappedComponent被渲染时,refs回调函数就会被执行,因此我们拿到一份WrappedComponent的实例引用:

const myContainer = (WrappedComponent) => (
    class extends Component{
        proc(WrappedComponentIntance) {
            WrappedComponentIntance.method();
        }
        render() {
            const newProps = Object.assign({},this.props,{
                ref: this.proc.bind(this)
            })
            return <WrappedComponent {...this.props} {...newProps}/>
        }
    }
)
复制代码

抽象state

const myContainer = (WrappedComponent) => (
    class extends Component{
        constructor(props) {
            super(props);
            this.state = {
                name: ''
            };
            this.changeName = this.changeName.bind(this);
        }
        changName(event) {
            this.setState({
                name: event.target.value
            })
        }
        render() {
            const newProps = {
                name: {
                    value: this.state.name,
                    onChange: this.changeName
                }
            };
            return <WrappedComponent {...this.props} {...newProps}/>
        }
    }
)
复制代码

同样我们还可以叠加样式:

return (<div className="my-class"><WrappedComponent {...this.props}/></div>)
复制代码

比较mixin和HOC的差别如图:

2、反向继承
先看代码:

const MyContainer = (WrappedComponent) => (
    class extends WrappedComponent{
        render() {
            return super.render()
        }
    }
)
复制代码

正如我们所见,高阶组件返回继承于WrappedComponent。在反向继承的方法中,高阶组件可以使用WrappedComponent的引用、state、props、生命周期和render方法。
渲染劫持 渲染劫持指的就是高阶组件可以控制WrappedComponent的渲染过程,并渲染出各种各样的结果。
我们来看第一个例子,他的用处就是如果用户已登录则显示否则返回空

const MyContainer = (WrappedComponent) => (
    class extends WrappedComponent{
        render(){
            return this.props.isLogedIn ? super.render() : null;
        }
    }
)
复制代码

第二个例子则是改变input中的值:

const MyContainer = (WrappedComponent) => (
    class extends WrappedComponent{
        render(){
            const elementsTree = super.render();
            let newProp {};
            if(elementsTree && elementsTree.type == 'input') {
                newProps = {value: 'hello world'}
            }
            const props = Object.assign({},elementsTree,newProps);
            const newElementsTree = React.cloneElement(elementsTree,props,elementsTree.props.children);
            return newElementsTree;
        }
    }
)
复制代码
  • 2.6组件的优化

1、纯函数
纯函数的三大原则:相同输入则相同输出,过程没有副作用(Immutable),没有额外的状态依赖。我们可以通过拆分组件为子组件,进而对组件做更细粒度化的控制。
2、PureRender
PureRender对object只作了引用比较,并没有做值的比较,

function shallowEqual(obj,newObj) {
    if(obj === newObj) {
        return true;
    }
    const objKeys = Object.keys(obj);
    const newObjKeys = Object.keys(newObj);
    
    if(objKeys.length !== newObjKeys.length ){
        return false;
    }
    
    return objKeys.every(key => {
        return obj[key] === newObj[key]
    })
}
复制代码

3、使用PureRender
利用createClass构建组件时,可以使用官方的插件react-addons-pure-render-mixin,像es6引入一样使用它,例子:

 import PureRenderMixin from 'react-addons-pure-render-mixin';
 ...
 construstor(props) {
     super(props);
     this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
 }
 ...
复制代码

4、优化PureRender
直接为props设置为对象或数组、设置props方法并通过事件绑定在元素上、设置子组件的shouldComponentUpdate为purerender 5、Immutable data
Immutable data有三种最重要的数据结构。Map(键值对集合,对应Object)、List(有序可重复列表,对应array)、ArraySet(无序且不可重复的列表)
immutable data的优点:降低了可变带来的复杂度、节省内存(Immutable使用结构共享尽量复用内存,没有被引用的对象将被垃圾回收)、撤销\重做,复制\粘贴、并发安全、拥抱函数式编程
6、Immutable和PureRender

7、设置key
动态列表将数据的id设置为key,如果key相同则React只会渲染第一个key,使用React的插件createFragment来解决两个子组件要渲染的情况

import createFragment from 'react-addons-create-fragment'
const children = createFragment({
    first:first,
    second:second
})
复制代码

8、使用react-addons-perf测试性能
react-addons-perf是官方提供的。通过Perf.start()和Perf.end()两个api来设置开始和结束状态。

Perf.printInclusive -> 打印出所有阶段时间
Perf.printExclusive -> 打印出初始化props、state、调用componentWillMount和componentDidMount方法的时间等。
Perf.printWasted -> 监测渲染内容不变的组件

  • 2.7动画

React提供的TransitionGroup能够帮助我们便捷地识别出增加或删除的组件。
1、对比css动画和js动画
css动画的局限性,包括(只支持cubic-bezier的缓动、css动画只针对一些特有的css属性)
2、cssanimation弥补了css transition上的不足
3、用js包装过的css动画,推荐库(react-smooth)
4、svg线条动画(vivus.js)

  • 2.7.2 玩转react transition

React Transition的生命周期componentWillAppear,componentDidAppear,componentWillEnter,componentDidEnter,componentWillLeave,componentDidLeave
React CSS Transition为子组件的每个生命周期加了不同的className,这样用户可以很方便地根据className的变化来实现动画。例如:

<ReactCSSTransitionGroup
 transitionName = 'example'
 transitionEnterTimeout = {400}>
 {items}
 </ReactCSSTransitionGroup>
 
 //相对应的scss代码为:
 .example-enter {
     transform: scaleY(0);
     &.example-enter-active {
         transform: scaleY(1);
         transition: transition .4s ease;
     }
 }
复制代码
  • 2.8自动化测试

1、Jest+ react-addons-test-utils(模拟浏览器时间和对dom的校验)
2、Enzyme (由airbnb公司提供) 使用类似jQuery api来操作dom

  • 2.8.3 自动化测试

持续集成服务器(CI)Travis CI 或 Circle CI

  • 小结

本章通过深入介绍React的概念及特性,让开发者对React组件开发有一个全面的认识并具备实践能力。

转载于:https://juejin.im/post/5adc1cee6fb9a07aa83e5098

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值