九.react起始简介

1. MVC的简介

MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。MVC被独特的发展起来用于映射传统的输入、处理和输出功能在一个逻辑的图形化用户界面的结构中。

M是指业务模型,V是指用户界面,C则是控制器,使用MVC的目的是将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式。

2.Vuex 和 Redux 的区别

Redux 使用的是不可变数据,而Vuex的数据是可变的。Redux每次都是用新的state替换旧的state,而Vuex是直接修改

Redux 在检测数据变化的时候,是通过的方式比较差异的,而其实和的原理一样,是通过来比较的(如果看源码会知道,其实他内部直接创建一个实例用来跟踪数据变化)

3. vue 和 react 监听数据的原理

  • vue中借助Object.defineProperty(getter setter) 做数据劫持

  • react很大程度上是通过比较对象的引用地址是否一致来看数据有没有变化(react中大量使用了不可变对象)

4. react 安装的目录

4.1 全局安装 webpack

1
2
3
4
5
6
2.1	构建webpack应用
运行npm init -y 快速初始化项目
npm install webpack@4.27.1 -d
npm install webpack-cli -d 提供一些基本命令(比如webpack命令)
npm install webpack-dev-server –d
npm i html-webpack-plugin@3.2.0 --save-dev

4.2 创建webpack 应用

1.src文件夹 目录下的 index.html 、main.js

  1. dist 文件夹 , webpack.config.js 配置如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    const path = require("path")
    var htmlWebpackPlugin = require('html-webpack-plugin');
    //webpack基于node构建的,所有用的是CommonJS模块化规范
    module.exports = {
    mode:"development",
    entry: path.join(__dirname, './src/main.js'),
    output: {
    path: path.join(__dirname, './dist'),
    filename: 'bundle.js'
    },
    plugins: [
    // 添加plugins节点配置插件
    new htmlWebpackPlugin({
    template:path.resolve(__dirname, 'src/index.html'),//模板路径
    filename:'index.html'//自动生成的HTML文件的名称
    })
    ],
    }
  2. 安装webpack和webpack-cli

    1
    2
    npm install webpack@4.27.1 -d
    npm install webpack-cli -d 提供一些基本命令(比如webpack命令)

    4.index.html

1
2
3
4
5
6
<head>
<meta charset="UTF-8">
<title>Title</title>

<script src="../dist/bundle.js"></script>
</head>
  1. webpack

4.3.配置webpack-dev-serve

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#1.安装webpack-dev-serve
npm install webpack-dev-server -d

#2.package.json的scripts添加dev
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack-dev-server --open --port 3000 --hot --host 127.0.0.1"
},
#3.npm run dev webpack-dev-server将打包后的bundle.js托管到项目根路径下(内存中),所以可以通过下面路径访问
http://localhost:8080/bundle.js

#4.修改index.html中引入bundle.js的路径
<head>
<meta charset="UTF-8">
<title>Title</title>

<!--使用webpack-dev-server打包后内存中的bundle.js-->
<script src="../bundle.js"></script>
</head>

4.4. 配置到内存中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#1.安装html-webpack-plugin
npm i html-webpack-plugin@3.2.0 --save-dev

#2.修改webpack.config.js配置 ★☆★☆★☆★
// 导入自动生成HTMl文件的插件 ★☆★☆★☆★
var htmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
mode:"development",
entry: path.join(__dirname, './src/main.js'),
output: {
path: path.join(__dirname, './dist'),
filename: 'bundle.js'
},
plugins: [
// 添加plugins节点配置插件
new htmlWebpackPlugin({
template:path.resolve(__dirname, 'src/index.html'),//模板路径
filename:'index.html'//自动生成的HTML文件的名称
})
],
}

5. 遇到问题报错如下

1
webpack-dev-server  不是内部或外部命令,也不是可运行 的程序 或批处理文件。

解决方法:

1
npm i webpack-dev-server -d

6.在 webpack 中使用 react

​ 1.运行 npm install react react-dom -S 安装包

  • react: 专门用于创建组件和虚拟DOM的,同时组件的生命周期都在这个包中

  • react-dom: 专门进行DOM操作的,最主要的应用场景,就是`ReactDOM.render()

    2.在html页面上准备一个容器

1
2
<!-- 容器,将来,使用 React 创建的虚拟DOM元素,都会被渲染到这个指定的容器中 -->
<div id="app"></div>

​ 3.main.js 中引入包

1
2
import React from 'react'                        // 创建组件、虚拟DOM元素,生命周期
import ReactDOM from 'react-dom' // 把创建好的 组件 和 虚拟DOM 放到页面上展示的

​ 4.创建虚拟 dom ,并且渲染到页面上去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React from 'react'                        // 创建组件、虚拟DOM元素,生命周期
import ReactDOM from 'react-dom' // 把创建好的 组件 和 虚拟DOM 放到页面上展示的

// 这是 创建虚拟DOM元素的 API <h1 title="啊,五环" id="myh1">你比四环多一环</h1>
// 第一个参数: 字符串类型的参数,表示要创建的标签的名称
// 第二个参数:对象类型的参数, 表示 创建的元素的属性节点
// 第三个参数: 子节点(包括 其它 虚拟DOM 获取 文本子节点)
// 参数n : 其它子节点


const myh3 = React.createElement('h2',{title:'这是title',id:'myh3'},'这是myh3,下面没有子元素')
const mydiv = React.createElement('div',{className:'color'},'这是一个div1的容器,下面有子元素',myh3)


// 3. 渲染虚拟DOM元素
// 参数1: 表示要渲染的虚拟DOM对象
// 参数2: 指定容器,注意:这里不能直接放 容器元素的Id字符串,需要放一个容器的DOM对象
ReactDOM.render(myh1, document.getElementById('app'))

//class使用 className

7. 虚拟 dom的介绍(提高性能)

React.creatElement()相当于在内存中去构建 dom 。 当页面将要呈现的时候:

1.浏览器会获取整个页面的内容

2.构建dom树 document文档对象 (将页面上的标签,属性,文本转换成 dom 对象)

3.在内存就好像如下的形式:

1
2
3
4
5
6
7
8
9
{
tagName:"div",
attrs:[
{ id : "box"},
{ name : "dongwenbin"}
{ class: ""}
],
children:[]
}

4.新旧的dom进行差异对比,产生出差异dom,更新真实的dom,最后视图的更新

8.JSX 语法

1.jsx语法:是一种符合 xml 规范的js语法 extend markup language 可拓展标记语言

1
2
3
4
1.安装babel:
npm i babel-core@6.26.3 babel-loader@7.1.5 babel-plugin-transform-runtime --save-dev

npm i babel-preset-env babel-preset-stage-0 --save-dev

2.安装能够识别转换jsx语法的包 babel-preset-react

1
npm i babel-preset-react -D

3.添加 .babelrc 文件配置文件如下:

1
2
3
4
{
"presets": ["env", "stage-0", "react"],
"plugins": ["transform-runtime"]
}

4.在 webpack.config.js中添加

1
2
3
4
5
module: { //要打包的第三方模块
rules: [
{ test: /\.js|jsx$/, use: 'babel-loader', exclude: /node_modules/ }
]
}

优点:不仅帮助我们创建虚拟dom,还允许我们在jsx中嵌入js表达式,js语法

缺点:其本质依然是借助React.createElement()来创建虚拟dom

在jsx中嵌入js代码 , 需要用{} , 数组里的 需要 key = “1”

1
2
3
4
5
6
7
8
9
10
11
{arrStr.map((item) => {
return <p key={item}>{item}</p>

})}
//数组的遍历 , 然后转换成指定的内容,也需要key 和 {} 进行才可以。

<label htmlFor="name">label</label>

{/* 注释 */}

只能有一个根标签,所有标签必须闭合

9.组件&Props

  1. 纯函数

    函数内部的代码不会对函数的入参以及函数外部的代码有任何的影响

  2. React 的组件

    2.1 函数组件(纯函数)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import React from 'react'                        // 创建组件、虚拟DOM元素,生命周期
    import ReactDOM from 'react-dom' // 把创建好的 组件 和 虚拟DOM 放到页面上展示的
    //react 中创建组件的第一种方式:使用纯函数
    function Hello(props) {
    console.log(props)
    return <div>{props.name}{props.age}</div>
    }

    export default Hello;
    //导出到外部的main.js中去接收

    2.2//展开运算符{…obj},节省了代码

    1
    2
    3
    ReactDOM.render(<Hello name={obj.name} age={obj.age} gender={obj.gender}></Hello>, document.getElementById('app'));

    ReactDOM.render(<Hello {...obj}></Hello>, document.getElementById('app'));

    2.2 类组件

    2.2.1 react中给我们了一个可以继承的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import React from 'react'
import ReactDOM from 'react-dom'

export default class Movie extends React.Component {
constructor() {
//super()需要是构造函数的第一行代码
super()

//使用class方式创建的组件允许组件有自己的state数据和action
//函数的形式不可以的有自己的数据
this.state = {
msg: '大家好,我是 class 创建的 Movie组件'
}
}

render() {
return <div>{this.state.msg}{this.props.name}</div>
}
}

//静态对象只属于类, 不属于创建new的对象
//静态方法,也是属于类,通过类名直接调用
//继承可以把公共的类放在Animal中 es6
//继承:class Dog extends Anmial
constructor(name,age,gender){
super(name,age);
//super调用父类的方法来初始化成员的信息
//继承的情况下必须使用super在第一行的创建,才可以创建
this.gender = gender;
}
9.1 两种创建的对比
  • 函数组件:又叫无状态组件、木偶组件、非受控组件 (只有props,没有state,没有生命周期钩子函数)
  • Class组件:有状态组件、智能组件、受控组件(有state,有生命周期钩子函数)

10. state 的数据更改问题

借助 this.setState 来修改 ,可以入参两个参数 ;

如果要立刻获取到更改完之后的数据,利用setState()函数指定第二个箭头参数 ,() => {}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 如果我们不基于以前的值来更改当前state的值,我们可以这样下
// this.setState({count:2})

// 如果我们需要根据以前的值来更改当前的state的值,我们可以向下面这样写:
// this.setState((state,props) => ({
// count : ++state.count
// }))

//如果我们想要修改state之后立马获取最新的数据,我们可以给setState()函数指定第二个参数
this.setState((state,props) => ({
count : ++state.count
}),()=>{
console.log(this.state.count)
})

//state的更新可能是异步的!!!!
//state的更新既可能是同步的,也可能是异步的。准确地说,在React内部机制能检测到的地方, setState就是异步的;在React检测不到的地方,例如setInterval,setTimeout里,setState就是同步更新的。”
// setTimeout(() => {
// this.setState({count:2})
// console.log(this.state.count) //因为setState()写在了setTimeout()函数里面,所以现在的setState()就是同步更新的
// }, 1000);
10.1 state 更新的原理
1
2
 setState本质是通过一个队列机制实现state更新的。 执行setState时,会将需要更新的state合并后放入状态队列,而不会立刻更新state,队列机制可以批量更新state。
如果不通过setState而直接修改this.state,那么这个state不会放入状态队列中,下次调用setState时对状态队列进行合并时,会忽略之前直接被修改的state,这样我们就无法合并了,而且实际也没有把你想要的state更新上去。
this.setState(newState)
newState 存入pending 队列 (待定队列)
判断是否处于 batch update (分批更新)
1.保存在 dirtyComponents 中 (Y) 2.遍历dirtyComponents ,调用updateComponent,更新 pending state or props (N)

10.1.1 如何知道已经被更新

  • 回调函数
  • 钩子函数

10.1.2 需要注意的地方

  • setState可能会引发不必要的渲染(renders)
  • setState无法完全掌控应用中所有组件的状态
10.2 updateComponent 中的 diff
(a) tree diff
1
2
3
其实传统diff算法就是对每个节点一一对比,循环遍历所有的子节点,然后判断子节点的更新状态。通过循环递归对节点进行依次对比,算法时间复杂度达到 O(n^3) ,n是树的节点数,这个有多可怕呢?——如果要展示1000个节点,得执行上亿次比较。即便是CPU快能执行30亿条命令,也**很难在一秒内**计算出差异。。

React 通过制定大胆的策略,将 O(n^3) 复杂度的问题转换成 O(n) 复杂度的问题。react根据自己的特点,实现了部分代码的简化。
1
2
那么问题来了,如果DOM节点出现了跨层级操作,diff会咋办呢?
答:diff只简单考虑同层级的节点位置变换,如果是跨层级的话,只有创建节点和删除节点的操作。
(b) component diff
1
2
3
4
React应用是基于组件构建的,对于组件的比较优化侧重于以下几点:
1. 同一类型组件遵从tree diff比较v-dom树
2. 不同类型组件,先将该组件归类为dirty component,替换下整个组件下的所有子节点
3. 同一类型组件Virtual Dom没有变化,React允许开发者使用shouldComponentUpdate()来判断该组件是否进行diff,运用得当可以节省diff计算时间,提升性能
1
如上图,当组件D → 组件G时,diff判断为不同类型的组件,虽然它们的结构相似甚至一样,diff仍然不会比较二者结构,会直接销毁D及其子节点,然后新建一个G相关的子tree,这显然会影响性能,官方虽然认定这种情况极少出现,但是开发中的这种现象造成的影响是非常大的。
(c) element diff
1
2
3
4
对于同一层级的element节点,diff提供了以下3种节点操作:
1. INSERT_MARKUP 插入节点
2. MOVE_EXISING 移动节点
3. REMOVE_NODE 移除节点
1
2
3
一般diff在比较集合[A,B,C,D]和[B,A,D,C]的时候会进行全部对比,即按对应位置逐个比较,发现每个位置对应的元素都有所更新,则把旧集合全部移除,替换成新的集合,如上图,但是这样的操作在React中显然是复杂、低效、影响性能的操作,因为新集合中所有的元素都可以进行复用,无需删除重新创建,耗费性能和内存,只需要移动元素位置即可。

React对这一现象做出了一个高效的策略:允许开发者对同一层级的同组子节点添加唯一key值进行区分。意义就是代码上的一小步,性能上的一大步,甚至是翻天覆地的变化!

#####