临时笔记-react实战

虚拟DOM

如果对DOM节点进行修改操作,一般情况下,每次修改都会直接操作DOM树。而React的虚拟DOM技术不一样,它会对比开始状态与最后的状态,当状态一致时,便不会对DOM树进行操作。最后的渲染结果是有diff算法控制的,React只对真正有改变的节点进行渲染。

搭建开发环境

方式一:React CDN资源

<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<!--编译JSX-->
<script src="https://cdn.bootcss.com/babel-standalone/6.26.0/babel.min.js"></script>

方式二:npm安装React

npm install --save react react-dom

方式三:create-react-app脚手架

npm install -g create-react-app
create-react-app myapp

端口号

在node_modules/react-scripts/scripts/start.js文件中可以修改默认端口号3000

目录说明

  • node_modules:放置项目需要的各种依赖模块
  • package-lock.json:记录实际安装的各个包的来源以及版本号。
  • package.json:定义依赖关系树,以及各个依赖模块的版本范围
  • public:里面的文件用于被index.html引用。
  • src:存放源码等,webpack只识别这个目录

调试

安装React Developer Tools的chrome插件

React事件系统

React事件系统在原生的DOM事件体系上封装了一个“合成事件”层,事件处理程序通过合成事件进行实例传递。它没有把所有事件绑定到对应的真实DOM上,而是使用委托机制实现统一的事件监听器,把所有的事件绑定到了最外层document上,然后再将事件进行分发。

React绑定事件的三种方法:

  • 组件上绑定
  • 构造函数中绑定
  • 箭头函数绑定

React原理

JSX

用JSX创建虚拟DOM提高了可读性:

  • JavaScript创建虚拟DOM

  • JSX创建虚拟DOM

书写JSX时,<script>的type为“text/babel”,把JSX转为浏览器能够理解的js代码

JSX样式:

<button style="background-color:red">按钮</button>
class Hello extends React.Component{
    render(){
        var bgColor={backgroundColor:"red"}
        return <button style={bgColor}>按钮</button>
    }
}
return <button style={{backgroundColor:"red"}}>按钮</button>

dom-diff

UI进行更新时,React会将当前数据和前一状态的数据进行对比,判断哪些数据发生了变化,接着把发生变化的数据渲染在UI上。Web界面实质上是构建的一棵DOM树,当某一节点发生变化时,React会对当前的DOM树和前一状态的DOM树进行比较,这个比较的算法就是dom-diff算法

React的diff算法的两个假设:

  • 不同类的元素会产生不同的DOM树
  • 同一层次的一组节点可以通过唯一的key值区分

在React之前有个标准dom-diff算法,针对任意两棵树找最小变化步骤,这个算法时间复杂度为O(n3)。而基于上述两条假设,React的diff算法可以将算法复杂度减少到O(n)。

React的同层次比较:

React对DOM树进行了分层,只会对同一层的节点进行数据比较。如果节点类型发生变化,React会将其删除,然后新建节点到新的DOM树上。如果节点类型相同、属性不同,那么React会进行替换操作。如果遇到同一层级的子节点进行操作时,需要加上key属性来进行唯一区别,否则React会进行告警。key的唯一属性是避免删除、创建等重复操作,减少性能消耗。

临时笔记-react实战

用示例解释React的dom-diff算法:

临时笔记-react实战

整个变化过程为:C节点从A节点转移到了D节点上。React对整棵DOM树的操作步骤为:

(1)删除C节点。

(2)创建C节点。

(3)更新B节点。

(4)更新A节点。

(5)渲染C节点。

(6)更新D节点。

(7)更新R节点。

setState

React组件可以理解为一个状态机。组件的更新其实就是内部state值的更新,state属性记录着组件的状态。而修改state的方法就setState。(示例)

class Like extends React.Component{
    constructor(){
        super()
        this.state={
            isLiked:true
        }
        this.changeState=this.changeState.bind(this)
    }
    changeState(){
        console.log(this.state.isLiked)//1.打印出true
        this.setState({
            isLiked:!this.state.isLiked
        })
        console.log(this.state.isLiked)//2.打印出true 可见是异步操作
    }
    render(){
        return(
            <input type="button" onClick={this.changeState} value={this.state.isLiked?"点赞":"取消"}/>
        )
    }
}
ReactDOM.render(<Like/>,document.getElementById('root'))

setState()方法为异步操作,它是通过一个队列机制来更新state。在执行changeState()方法中的this.setState时,React先把新state值放到了一个状态队列中,并没有及时更新state值。

React中的setState异步原理:

class Like extends React.Component{
    constructor(){
        super()
        this.state={
            count:0
        }
        this.showState=this.showState.bind(this)
    }
    componentDidMount(){
        this.setState({count:this.state.count+1})
        this.setState({count:this.state.count+1})
        this.setState({count:this.state.count+1})
        console.log('setState后:',this.state.count) 
    }
    showState(){
        console.log('showState中的state后:',this.state.count)
    }
    render(){
        return(
            <input type="button" onClick={this.showState} value="显示state"/>
        )
    }
}

临时笔记-react实战

结果可以看出,虽然在componentDidMount()方法中执行了三次setState,但是最后就执行了一次,并且没有立即执行。原因是React先把新的state值放到了状态队列里,最后对三次setState进行了批量处理,批处理过程中进行了合并,所以结果就是对setState只执行了一次,这样做的好处是可以避免一些重复渲染setState()可以理解为一个重新渲染的请求,而不是立即更新的一个命令,为了有更好的性能,React可能会推迟执行,然后会批量进行处理。React不会保证在setState操作后立即拿到最新的state值。

如果想在setState后立即得到最新的state值,可以通过函数来实现:

this.setState(
    function(state){
        return {
            count:state.count+1
        }
    }
);

在实际项目中不要用this.state来修改state值,因为获取到的this.state有可能不是最新的,更新组件state时要用setState()方法。

React组件写法

  • React.Component写法

    class HelloWord extends React.Component{
        state={message:'你好呀!'}
    	render(){
        	return <h1>{this.state.message}</h1>
    	}
    }
    
  • 无状态函数写法(没有state参与,只接受props,以减少耦合度)

    const HelloWord=(props)=>{
    	return <h1>你好啊!{props.name}</h1>
    }
    
    function HelloWord(props) {
        return <h1>你好啊!{props.name}</h1>
    }
    

React组件分类

React的组件分类,主要是以两方面为标准的:一方面是呈现,一方面是管理。按这种标准可分为木偶组件和智能组件(对比示例)

  • 引入:获取数据与ui渲染放在一起,显得很臃肿。如果把获取数据和UI渲染分开,分别放在一个独立的组件中,代码的可读性、可维护性、可复用性更好

  • 木偶组件:呈现UI职责,不涉及数据操作

  • 智能组件:更多考虑逻辑操作性问题

MVC框架

MVC框架是业界广泛接受的一种前端应用框架类型,这种框架把应用分为三个部分:

  • Model(模型)负责管理数据,大部分业务逻辑也应该放在Model中;
  • View(视图)负责渲染用户界面,应该避免在View中涉及业务逻辑;
  • Controller(控制器)负责接受用户输入,根据用户输入调用对应的Model部分逻辑,把产生的数据结果交给View部分,让View渲染出必要的输出。

临时笔记-react实战

MVC框架提出的数据流很理想,用户请求先到达Controller,由Controller调用Model获得数据,然后把数据交给View,但是,在实际框架实现中,总是允许View和Model可以直接通信

在图中,可以看到Model和View之间缠绕着蜘蛛网一样复杂的依赖关系,根据箭头的方向,我们知道有的是Model调用了View,有的是View调用了Model

临时笔记-react实战

Flux框架

Mobx框架

Mobx是一个用于状态管理的库,和React是一对很搭的组合。React在View层提供了很好的解决方案,Mobx对状态管理具有极大的简易性和可扩展性,二者搭配,能够很好地管理一些大型前端项目。Mobx的工作原理大概如下图示。在整个数据流中,首先用户的触发事件到达Actions中,然后依据事件属性及事件要求在State中对状态值进行修改,接下来用新的State数据计算所需要的Computed values,最后响应渲染UI视图层:

临时笔记-react实战

Redux框架

Redux生态

redux middleware

Redux主要输出createStore、combineReducers、bindActionCreators、applyMiddleware、compose五个接口。

Redux的核心是控制和管理所有的数据输入与输出,使用纯函数dispatch派发action来更改数据,其功能简单且固定。middleware允许我们dispatch一个action之后,在到达reducer之前先做一些额外的处理,可以理解为每一个middleware都在增强dispatch的功能

redux-logger

能够对所有action发生后生成的state进行记录,即日志中间件

redux-thunk

用于在redux中处理异步action的中间件,异步action的场景包括需要在action中执行setTimeout,以及需要通过fetch方法调用服务端API等,对于异步action的场景可以使用redux-thunk中间件来优化代码流程

redux-saga

与redux-thunk功能类似,也是主要集中管理React应用中的异步操作。redux-saga最大的特点是使用generator(ES6)的形式,采用监听的形式进行工作,可以使用同步的方式编写异步代码,使得项目流程拆分更细,应用的逻辑和视图拆分更加清晰,分工明确。

React架构

React不仅仅是一个库,也不是一个框架,而是一个庞大的生态体系。若希望充分使用React尽可能多的特性,整个技术栈都要相应地配合改造,则我们要学习从后端到前端的一整套解决方案。因此,使用React时,合理的选择是采用React的整个技术栈。

一、文件结构

采用React+Redux+React-Router+Less+ES6+webpack介绍React实现一个完整应用的文件结构。React的CLI脚手架工具create-react-app屏蔽了和React无关的配置,如Babel、webpack。下面我们使用这个工具快速创建一个项目:

npm install -g create-react-app
create-react-app hello-world

临时笔记-react实战

二、CSS方案

一些css相关的问题有:全局污染、命名混乱、依赖引入复杂、无法共享变量、代码冗余。

CSS Modules是一种非常优秀的模块化解决方案。CSS Modules结合了CSS生态和JavaScript模块化能力,使用JavaScript来管理样式依赖,加入了局部作用域和模块依赖。CSS Modules发布时依旧编译出单独的JavaScript和CSS。

局部样式

CSS Modules通过使用一个独一无二的class的名字产生局部作用域:

import React from 'react'
import style from './App.css'

export default()=>{
    return(
    	<div className={style.wrapper}>
            <h1 className={style.title}>Hello!</h1>
        </div>
    )
}

其中App.css:

.wrapper{
    width:750px;
    margin:0 auto;
}
.title{
    color:red;
    font-size:48px;
}

webpack.config.js中配置css-loader启用CSS Modules:

module.export={
    entry:__dirname+'/index.js',
    output:{
        publicPath:'/',
        filename:'./bundle.js'
    },
    module:{
        loaders:[
            {
                test:/\.jsx?$/,
                exclude:/node_modules/,
                loader:'babel',
                query:{
                    preset:['es2015','stage-0','react']
                }
            },
            {
                test:/\.css$/,
                loader:"style-loader!css-loader?modules&localIdentName=[name]__[local]-[hash:base64:5]"
            }
        ]
    }
}

localIdentName用于设置生成样式的命名规则,css-loader默认的哈希算法是[hash:base64],这会将.title编译成._3zyde4l1yATCOkgn-DBWEL这样的字符串。本示例中我们自定义的编译规则生成的HTML是,App.css也会同时被编译。CSS Modules对CSS中的class名都做了处理,使用对象来保存原class和混淆后class的对应关系:

通过这些处理,CSS Modules可以继续使用CSS编写样式,相当于给每个class名外加了一个:local,使得所有样式都是局部样式,以此来实现样式的局部化,解决了命名冲突和全局污染问题。

全局作用域

CSS Modules使用:global(.className)的语法,声明一个全局规则。凡是这样声明的class,都不会被编译成哈希字符串:

.normal{
    color:green
}
//等价
:local(.normal){
    color:green
}
:global(.btn){
    color:red;
}
//定义多个全局样式
:global{
    .link{
        color:red;
    }
    .box{
        color:yello
    }
}

组合样式

在CSS Modules中,一个选择器可以继承另一个选择器的规则,称为“组合”。让.title继承.titleBase:

.base{
    background-color:blue;
}
.title{
    composes:base;
    color:red
}
.wrapper{
    composes:base;
    width:750px
}

PostCSS

PostCSS是一款对CSS进行处理的工具,主要依赖插件来进行操作。PostCSS是一个利用JavaScript插件来对CSS进行转换的工具,较常使用的插件有CSS Modules、Autoprefixer、postcss-cssnext等。

CSS Modules

npm install --save postcss-loader postcss-modules-values

在webpack.config.js中增加postcss-loader配置:

var values=require('postcss-modules-values')
module.export={
    entry:__dirname+'/index.js',
    output:{
        publicPath:'/',
        filename:'./bundle.js'
    },
    module:{
        loaders:[
            {
                test:/\.jsx?$/,
                exclude:/node_modules/,
                loader:'babel',
                query:{
                    preset:['es2015','stage-0','react']
                }
            },
            {
                test:/\.css$/,
                loader:"style-loader!css-loader?modules!postcss-loader"
            }
        ]
    },
    postcss:{
        values
    }
}

定义变量

@value blue #0c77f8
@value red #ff0000

引用

@values colors:"./colors.css";
@values blue,red from colors;
.title{
    color:red;
    background-color:blue
}

Autoprefixer

针对浏览器兼容的处理可以使用Autoprefixer插件。Autoprefixer是一个根据Can IUse(http://caniuse.com)兼容性解析CSS,然后为其添加浏览器厂商前缀的PostCSS插件。

::example{
    display:none;
    position:relative;
    transform:translate(10,10)
}

Autoprefixer将使用基于当前浏览器支持的特性和属性数据来添加前缀,处理之后生成:

.example{
    display:none;
    position:relative;
    -webkit-transform:translate(10,10);
    -ms-transform:translate(10,10);
    transform:translate(10,10);
}

三、状态管理

四、路由管理

上一篇:React学习之旅Part7:React绑定事件和使用setState()修改state的数据、React实现双向数据绑定


下一篇:前端笔记(关于受控组件与非受控组件)