第5章 虚拟 DOM
- vdom 是 vue 和 React 的核心
- vdom 比较独立,使用也比较简单
- vdom 是 vue 和 React 的核心实现
题目
什么是 vdom,为何要用 vdom?
- 什么是 vdom
virtual dom , 虚拟 DOM
用 JS 模拟 DOM 结构
DOM 变化的对比,放在 JS 层来做(图灵完备语言)
提高重绘性能 ( DOM 操作是浏览器最耗费性能的操作 )
- Item 1
- Item 2
// 用 JS 模拟 DOM 结构
{
tag: 'ul',
attrs: {
id: 'list'
},
children: [
{
tag: 'li',
attrs: {className: 'item'},
children: ['Item 1']
},
{
tag: 'li',
attrs: {className: 'item'},
children: ['Item 2']
}
]
}
- 设计一个需求场景
// 1. 将数据展示成一个表格。2. 随便修改一个信息,表格也跟着修改
[
{
name: '张三',
age: '20',
address: '北京'
},
{
name: '李四',
age: '21',
address: '上海'
},
{
name: '王五',
age: '22',
address: '广州'
}
]
- 用 jQuery 实现
// 渲染函数
function render(data) {
// 此处省略 N 行
}
// 修改信息
$('#btn-change').click(function () {
data[1].age = 30;
data[2].address = '深圳';
render(data);
});
// 页面加载完成之后,立即执行 render
render(data);
// 渲染函数
function render(data) {
var $container = $('#container');
// 清空现用内容
$container.html('');
// 拼接 table
var $table = $('');
$table.append($('name age address '));
data.forEach(function (item) {
$table.append($('' + item.name + ' ' + item.age + ' ' + item.address + ' '));
});
// 渲染到页面
$container.append($table);
}
- 遇到的问题
var div = document.createElement('div');
var item, result = '';
for (item in div) {
result += ' | ' + item;
}
console.log(result);
DOM 操作是“昂贵”的,js 运行效率高
尽量减少 DOM 操作,而不是“推倒重来”
项目越复杂,影响就越严重
vdom 即可解决这个问题
vdom如何使用,核心 API 有哪些?
- vdom 是一类技术实现
- 介绍 snabbdom——一个开源的 v-dom库
https://github.com/snabbdom/snabbdom
// vdom:用 JS 模拟的 DOM 结构
// vnode:用 JS 模拟的 DOM 节点
// h 函数(参数1=选择器,参数2=事件/样式/属性,参数3=变量或数组) {}
// patch 的两种用法:1.初次渲染。2.再次对比
var container = document.getElementById('container');
var vnode = h('div#container.two.classes', {on: {click: someFn}}, [
h('span', {style: {fontWeight: 'bold'}}, 'This is bold'),
' and this is just normal text',
h('a', {props: {href: '/foo'}}, 'I\'ll take you places!')
]);
// Patch into empty DOM element – this modifies the DOM as a side effect
patch(container, vnode);
var newVnode = h('div#container.two.classes', {on: {click: anotherEventHandler}}, [
h('span', {style: {fontWeight: 'normal', fontStyle: 'italic'}}, 'This is now italic type'),
' and this is still just normal text',
h('a', {props: {href: '/bar'}}, 'I\'ll take you places!')
]);
// Second `patch` invocation
patch(vnode, newVnode); // Snabbdom efficiently updates the old view to the new state
This is bold
and this is just normal text
I'll take you places!
- 介绍 snabbdom - h 函数
// snabbdom 简化版
var node = h('ul#list', {}, [
h('li.item', {}, 'Item 1'),
h('li.item', {}, 'Item 2')
]);
// 用 JS 模拟出来的 DOM 结构
{
tag: 'ul',
attrs: {
id: 'list'
},
children: [
{
tag: 'li',
attrs: {className: 'item'},
children: ['Item 1']
},
{
tag: 'li',
attrs: {className: 'item'},
children: ['Item 2']
}
]
}
- 介绍 snabbdom - patch 函数
// snabbdom 简化版
var node = h('ul#list', {}, [
h('li.item', {}, 'Item 1'),
h('li.item', {}, 'Item 2')
]);
var container = document.getElementById('container');
patch(container, vnode);
// 模拟改变
var btnChange = document.getElementById('btn-change');
btnChange.addEventListener('click', function () {
var newVnode = h('ul#list', {}, [
h('li.item', {}, 'Item 111'),
h('li.item', {}, 'Item 222'),
h('li.item', {}, 'Item 333')
]);
patch(vnode, newVnode);
});
- 完整使用 snabbdom
v-dom
- 重做之前的 demo
v-dom
var vnode;
// 定义渲染函数
function render(data) {
var newVnode = h('table', {}, data.map(function (item) {
var tds = [];
var i;
for (i in item) {
if (item.hasOwnProperty(i)) {
tds.push(h('td', {}, item[i] + ''));
}
}
return h('tr', {}, tds);
}));
if (vnode) {
patch(vnode, newVnode);
} else {
patch(container, newVnode);
}
vnode = newVnode;
}
- 核心 API
h(‘<标签名>’, {…属性…}, […子元素…]);
h(‘<标签名>’, {…属性…}, ‘….’);
patch(container, vnode);
patch(vnode, newVnode);
了解 diff 算法吗?
- 什么是 diff 算法
// Linux 里古老的 diff 命令,可以比较两个文本的不同
>diff log1.txt log2.txt
// git diff 版本比较
>git diff ./src/index.js
// 在线 diff 对比器
https://tool.oschina.net/diff/
- 去繁就简
diff 算法非常复杂,实现难度很大,源码量很大
去繁就简,讲明白核心流程,不关心细节
面试官也大部分都不清楚细节,但是很关心核心流程
去繁就简之后,依然具有很大挑战性,并不简单
- vdom 为何用 diff 算法
DOM 操作是“昂贵”的,因此尽量减少 DOM 操作
找出本次 DOM 必须更新的节点来更新,其他的不更新
这个“找出”的过程,就需要 diff 算法
- diff 算法的实现流程
1.patch(container, vnode);
2.patch(vnode, newVnode);
- 虚拟 DOM 如何变成的真实 DOM
- Item 1
// 用 JS 模拟 DOM 结构
{
tag: 'ul',
attrs: {
id: 'list'
},
children: [
{
tag: 'li',
attrs: {className: 'item'},
children: ['Item 1']
}
]
}
// vnode 参数就是类似上方的结构
function createElement(vnode) {
var tag = vnode.tag;
var attrs = vnode.attrs || {};
var children = vnode.children || [];
if (!tag) {
return null;
}
var elem = document.createElement(tag);
var attrName;
for (attrName in attrs) {
if (attrs.hasOwnProperty(attrName)) {
elem.setAttribute(attrName, attrs[attrName]);
}
}
children.forEach(function (childVnode) {
// 递归调用 createElement 创建子元素
elem.appendChild(createElement(childVnode));
});
return elem;
}
虚拟 DOM 和真实 DOM 有对应关系
- 新的虚拟 DOM 和旧的 DOM 比较
function updateChildren(vnode, newVnode) {
var children = vnode.children || [];
var newChildren = newVnode.children || [];
children.forEach(function (child, index) {
var newChild = newChildren[index];
if (newChild == null) {
return
}
if (child.tag === newChild.tag) {
updateChildren(child, newChild);
} else {
replaceNode(child, newChild);
}
});
}
解答
- 知道什么是 diff 算法,是 linux 的基础命令
- vdom 中应用 diff 算法是为了找出需要更新的节点
- 实现,patch(container, vnode) 和 patch(vnode, newVnode)
- 核心逻辑,createElement 和 updateChildren
第6章 MVVM
题目
之前使用 jquery 和现在使用 Vue 或 React 框架的区别?
- jQuery 实现 todo-list
- vue 实现 todo-list
- {{ item }}
jQuery 和框架的区别
数据和视图分离 - 解耦
以数据驱动视图 - 封装DOM 操作
你如何理解MVVM?- 联系 View 和 Model
View 可以通过 事件绑定 的方式影响 Model
Model 可以通过 数据绑定 的方式影响 View
第7章 组件化和 React
- 是否做过 React 开发?
- React 以及组件化的一些核心概念
- 实现流程
//>todo
//>——index.js
import React, { Component } from 'react'
import Input from './input/index.js'
import List from './list/index.js'
// class Component {
// constructor(props) {
// }
// renderComponent() {
// const prevVnode = this._vnode
// const newVnode = this.render()
// patch(prevVnode, newVnode)
// this._vnode = newVnode
// }
// }
class Todo extends Component {
constructor(props) {
super(props)
this.state = {
list: ['a', 'b']
}
}
render() {
return (
)
/*
React.createElement(
"div",
null,
React.createElement(Input, { addTitle: this.addTitle.bind(this) }),
React.createElement(List, { data: this.state.list })
);
*/
// React.createElement(List, { data: this.state.list })
// var list = new List({ data: this.state.list })
// var vnode = list.render()
}
addTitle(title) {
const currentList = this.state.list
this.setState({
list: currentList.concat(title)
}
// , () => {
// // console.log(this.state.list)
// this.renderComponent()
// }
)
}
}
export default Todo
//>todo
//>——list
//>————index.js
import React, { Component } from 'react'
class List extends Component {
constructor(props) {
super(props)
}
render() {
const list = this.props.data
return (
{
list.map((item, index) => {
return - {item}
})
}
)
/*
React.createElement(
"ul",
null,
list.map((item, index) => {
return React.createElement(
"li",
{ key: index },
item
);
})
);
*/
}
}
export default List
//>todo
//>——input
//>————index.js
import React, { Component } from 'react'
class Input extends Component {
constructor(props) {
super(props)
this.state = {
title: ''
}
}
render() {
return (
)
}
changeHandle(event) {
this.setState({
title: event.target.value
})
}
clickHandle() {
const title = this.state.title
const addTitle = this.props.addTitle
addTitle(title) // 重点!!!
this.setState({
title: ''
})
}
}
export default Input
对组件化的理解?
- 组件的封装
视图
数据
变化逻辑(数据驱动视图变化)
- 组件的复用
props 传递
复用
JSX 是什么?
- JSX 语法
html 形式
引入 JS 变量和表达式
if…else…
循环
style 和 className
事件
JSX 语法根本无法被浏览器所解析
那么它如何在浏览器运行?
- JSX 解析成 JS
- 独立的标准
JSX 是 React 引入的,但不是 React 独有的
React 已经将它作为一个独立标准开放,其他项目也可用
React.createElement 是可以自定义修改的
说明:本身功能已经完备;和其他标准监控和扩展性没问题
另:有机会录制《1000行代码实现React》,就用 JSX 标准
JSX 和 vdom 什么关系?
简述 React 的 setState?
简述自己如何比较 React 和 Vue?