框架

318 阅读9分钟

前端主流框架

一、Vue.js

vue全览

(一) 生命周期钩子函数

(1) beforeCreate:无法获取 props 或 data。
(2) created:可以访问 props 和 data,组件还没被挂载。
(3) beforeMount :开始创建 VDOM。
(4) mounted:并将 VDOM 渲染为真实DOM并且渲染数据。
(5) beforeUpdate:数据更新前。
(6) updated:数据更新后。
(7) beforeDestroy:组件销毁前,适合移除事件、定时器等等。
(8) destroyed:组件销毁后。

(二) 组件间通信

1.父子组件之间

// 父组件 to 子组件
<!--父组件中-->
<son :toSon="msg" @toParent="print"></son>
<script>
  data() {
    return {
      msg: 'parent2son'
    }
  },
  methods:{
    print(val){
      this.msg = val
    }
  }
</script>

<!--子组件中-->
props: ['toSon'],
mounted(){
    this.$emit('toParent','son2parent')
}
</script>

<!--等价于-->
// sync 语法糖:当一个子组件需要改变了一个 prop 的值时,会通知其父组件进行同步的修改
<!--父组件中-->
<son :change.sync="msg"></son>
<script>
  data() {
    return {
      msg: 'parent2son'
    }
  }
</script>
<!--子组件中-->
props: ['change'],
mounted(){
  this.$emit('update:change','son2parent')
}

2.任意组件之间

// 无论相隔多少层都能取到 eventBus
<!--父组件中-->
<son></son>
<script>
  provide() {
    return {
      eventBus: this.eventBus
    }
  },
  data() {
    return {
      eventBus: new Vue(),
      msg: 'parent2son'
    }
  },
  mounted(){
    this.eventBus.$on('change',(val)=>{
      this.msg = val
    })
  }
</script>
<!--子组件中-->
inject: ['eventBus'],
data() {
  return {
    msg: 'son2parent'
  }
},
mounted(){
  this.eventBus.$emit('change', this.msg)
}

(三) extend

使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。

<div id="mount-point"></div>
<script>
// 创建构造器
var Profile = Vue.extend({
  template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
  data: function () {
    return {
      firstName: 'Walter',
      lastName: 'White',
      alias: 'Heisenberg'
    }
  }
})
// 创建 Profile 实例,并挂载到一个元素上。
new Profile().$mount('#mount-point')
</script>

(四) mixin(混入) 和 组件引用

1. 引用

组件在引用之后相当于在父组件内开辟了一块单独的空间,来根据父组件props过来的值进行相应的操作,单本质上两者还是泾渭分明,相对独立。

2. 混入

mixins则是在引入组件之后,则是将组件内部的内容如data等方法、method等属性与父组件相应内容进行合并。相当于在引入后,父组件的各种属性方法都被扩充了。

全局混入:混入也可以进行全局注册,一旦使用全局混入,它将影响每一个之后创建的 Vue 实例。

3. 区别

单纯组件引用:父组件 + 子组件 >>> 父组件 + 子组件
mixins:父组件 + 子组件 >>> new父组件

// 定义一个混入对象
var myMixin = {
  created: function () {
    this.hello()
  },
  methods: {
    hello: function () {
      console.log('hello from mixin!')
    }
  }
}

// 定义一个使用混入对象的组件
var Component = Vue.extend({
  mixins: [myMixin]
})

var component = new Component() // => "hello from mixin!"

(五) computed 和 watch 区别

  • computed 是计算属性,依赖其他属性计算值,并且 computed 的值有缓存,只有当计算值变化才会返回内容。
  • watch 监听到值的变化就会执行回调,在回调中可以进行一些逻辑操作。

(六) keep-alive

如果你需要在组件切换的时候,保存一些组件的状态防止多次渲染,就可以使用 keep-alive 组件包裹需要保存的组件。

声明周期钩子函数

  • activated: 被 keep-alive 缓存的组件激活时调用
  • deactivated: 被 keep-alive 缓存的组件停用时调用

(七) v-show 与 v-if 区别

  • v-show: 只是在 display: none 和 display: block 之间切换。无论初始条件是什么都会被渲染出来。
  • v-if: 当属性初始为 false 时,组件就不会被渲染,直到条件为 true,并且切换条件时会触发销毁/挂载组件。

(八) 组件中 data 什么时候可以使用对象

组件复用时所有组件实例都会共享 data,如果 data 是对象的话,就会造成一个组件修改 data 以后会影响到其他所有组件,所以需要将 data 写成函数,每次用到就调用一次函数获得新的数据。

(九) 变化侦测

1.当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。

2.每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把 getter 过的数据属性记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。

1. ES5 的 object.defineProperty

语法

Object.defineProperty(obj, key, descriptor)

参数

  • obj: 目标对象
  • key: 需要操作的目标对象的属性名
  • descriptor: 描述符

返回值

修改后的 obj

属性描述符

  • configurable:当且仅当 configurable 为 true 时,该属性描述符(descriptor) 才能够被改变,同时该属性(key) 也能被删除。默认为 false。
  • enumerable:当且仅当 enumerable 为 true 时,该属性(key) 才能够出现在对象的枚举属性中。默认为 false。
  • value:该 key 对应的 value。默认为 undefined。
  • writable:当且仅当 writable 为 true 时,value 才能被赋值运算符改变。默认为 false。
  • get:一个给属性提供getter的方法。
  • set:一个给属性提供 setter 的方法。
var a = { a: 111 }
Object.defineProperty(a, 'a', {
    get: function() {
        console.log('get!')
    },
    set: function (val) {
        console.log('set ' + val + '!')
    }
})
a.a // get!
a.a = 222 // set 222!

2. ES6 的 Proxy

(1) Proxy 无需一层层递归为每个属性添加代理,一次即可完成以上操作,性能上更好。
(2) Proxy 可以完美监听到任何方式的数据改变。

语法

let p = new Proxy(target, handler)

参数

target:用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理) 。

handler:定义 target 的拦截行为

Proxy 支持的拦截操作:

  • get(target, propKey, receiver):get方法用于拦截某个属性的读取操作。
  • set(target, propKey, value, receiver):set方法用来拦截某个属性的赋值操作。
  • apply方法拦截函数的调用、call和apply操作。
  • has(target, propKey):判断对象是否具有某个属性时,这个方法会生效,返回一个布尔值。
  • construct(target, args):construct方法用于拦截new命令。
  • deleteProperty(target, propKey):拦截delete操作。
  • defineProperty(target, propKey, propDesc):defineProperty方法拦截了Object.defineProperty操作,返回一个布尔值。
  • getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor()。
  • getPrototypeOf(target):拦截获取对象原型。
  • isExtensible(target):拦截Object.isExtensible(proxy)。
  • ownKeys(target):拦截对象自身属性的读取操作。
  • preventExtensions(target):拦截Object.preventExtensions()。
  • setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto)。
let handler = {
    get: function(target, name){
        console.log('getter!' + name)
    },
    set: function(target, name){
        console.log('setter!' + name)
    }
}

let p = new Proxy({ 'a': 1 }, handler)  

p.a // getter!a
p.a = 2 // setter!a

3. Object.defineProperty 的缺陷

如果通过下标方式修改数组数据或者给对象新增属性并不会触发组件的重新渲染,因为 Object.defineProperty 不能拦截到这些操作。

3.1 Object 解决方案

vm.$set( target, propertyName/index, value )

在object上设置一个属性,如果object是响应式的,则该属性被创建后也是响应式的。

var vm = new Vue({
  data:{
    obj: {
        a: 1
    }
  }
})  // `vm.obj.a` 是响应式的

vm.obj.b = 2 // `vm.obj.b` 是非响应式的

this.$set(this.obj,'c',3) // `vm.obj.c` 是响应式的

vm.$watch( expOrFn, callback, [options] )

  • callback:参数为新值和旧值。
  • options - deep:为了发现对象内部值的变化,可以在选项参数中指定 deep: true 。注意监听数组的变动不需要这么做。
  • options - immediate:在选项参数中指定 immediate: true 将立即以表达式的当前值触发回调
vm.$watch('a.b.c', function (newVal, oldVal) {
  // 做点什么
})

vm.$delete( target, propertyName/index )

删除数据中的某个属性,并且此时vue可以侦测到该数据发生了变化。

3.2 Array 解决方案

改变自身数组内容的方法有7个,分别是push、pop、shift、unshift、splice、sort、reserve,针对需要侦测变化的数据,使覆盖原型方法。

(十) 模板编译

直接把模板丢到浏览器中肯定是不能运行的,模板只是为了方便开发者进行开发。Vue 会通过编译器将模板通过几个阶段最终编译为 render 函数,然后通过执行 render 函数生成 Virtual DOM 最终映射为真实 DOM。

1. 解析(parse)

首先是 parse,parse 会用正则等方式将 template 模板中进行字符串解析,得到指令、class、style等数据,形成抽象语法树(AST)。

2. optimize(AST)

我们需要为静态的节点做上一些「标记」,在 patch 的时候我们就可以直接跳过这些被标记的节点的比对,从而达到「优化」的目的。经过 optimize 这层的处理,每个节点会加上 static 属性,用来标记是否是静态的。

3. generate(生成)

将 AST 转换为 render 函数。

(十一) nextTick 原理

(1) vue用异步队列的方式来控制DOM更新和nextTick回调先后执行。
(2) microtask因为其高优先级特性,能确保队列中的微任务在一次事件循环前被执行完毕。
(3) 因为兼容性问题,vue不得不做了microtask向macrotask的降级方案。

(十二) 路由

全局守卫:

  • 全局前置守卫 beforeEach (to, from, next)
  • 全局解析守卫 beforeResolve
  • 全局后置钩子 afterEach(to, from)

路由独享的守卫

  • beforeEnter(to, from, next)

组件内守卫:

  • 到达这个组件 beforeRouteEnter:(to,from,next)=>{}
  • 在当前路由改变,但该组件被复用 beforeRouteUpdate (to, from, next) {}
  • 离开这个组件时,beforeRouteLeave:(to,from,next)=>{}

路由传参

path:'/:name'
this.$route.params.name

(十三) vuex

vuex是一个专为vue.js应用程序开发的状态管理模式

1.state:存储数据,存储状态

this.$store.state

2.getter:可以认为是 store 的计算属性,它的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
3.mutation:更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。

this.$store.commit('state', XXX)

4.action:包含任意异步操作,通过提交 mutation 间接更变状态。

this.$store.dispatch(XXX)

5.module:将 store 分割成模块,每个模块都具有state、mutation、action、getter、甚至是嵌套子模块。

6.原理

(1) 在 Store 的构造函数中对 state 进行「响应式化」。
(2) state 会将需要的依赖收集在 Dep 中,在被修改时更新对应视图。
(3) 在全局有一个 globalData,它被传入一个 Vue 对象的 data 中,之后在任意 Vue 模板中对该变量进行展示。

(十四) 双向绑定

双向绑定

<input type="text" :value="name" @input="name = $event.target.value">

二、React.js

(一) 生命周期

React v16.3入了两个新的生命周期函数,用一个静态函数getDerivedStateFromProps来取代被deprecate的几个生命周期函数,就是强制开发者在render之前只做无副作用的操作,而且能做的操作局限在根据props和state决定新的state:

  1. getDerivedStateFromProps
  2. getSnapshotBeforeUpdate
/** getDerivedStateFromProps **/
static getDerivedStateFromProps(nextProps, prevState) {
  //根据nextProps和prevState计算出预期的状态改变,返回结果会被送给setState
}

/** getSnapshotBeforeUpdate **/
getSnapshotBeforeUpdate(prevProps, prevState) {
    if (...) {
      some code...
      return list.scrollHeight - list.scrollTop
    }
    return null
}
componentDidUpdate(prevProps, prevState, snapshot) {
  some code...
}

/** shouldComponentUpdate **/
shouldComponentUpdate(nextProps, nextState){
  some code...
}

当 props 或 state 发生变化时,shouldComponentUpdate() 会在渲染执行之前被调用。返回值默认为 true。首次渲染或使用 forceUpdate() 时不会调用该方法。
根据 shouldComponentUpdate() 的返回值,判断 React 组件的输出是否受当前 state 或 props 更改的影响。默认行为是 state 每次发生变化组件都会重新渲染。

同时deprecate了一组生命周期API,包括:

  1. componentWillReceiveProps
  2. componentWillMount
  3. componentWillUpdate

生命周期

(二) setState

setState 在 React 中是经常使用的一个 API,这个 API 是异步的。

(二) 性能优化

shouldComponentUpdate 会检查指定字段是否改变。如果这些值没有改变,那么这个组件不会更新。
如果你的组件更复杂一些,你可以使用类似“浅比较”的模式来检查 props 和 state 中所有的字段,以此来决定是否组件需要更新。React 已经提供了一位好帮手来帮你实现这种常见的模式 - 你只要继承 React.PureComponent 就行了。

浅比较

检查原始值是否有相同的值(例如:1 == 1或者ture==true),数组和对象引用是否相同。

(三) 跨组件通信

1.父子通信

父组件通过 props 传递数据给子组件,子组件通过调用父组件传来的函数传递数据给父组件,这两种方式是最常用的父子通信实现办法。

这种父子通信方式也就是典型的单向数据流,父组件通过 props 传递数据,子组件不能直接修改 props, 而是必须通过调用父组件函数的方式告知父组件修改数据。

2.兄弟组件通信

对于这种情况可以通过共同的父组件来管理状态和事件函数。比如说其中一个兄弟组件调用父组件传递过来的事件函数修改父组件中的状态,然后父组件将状态传递给另一个兄弟组件。

3.跨多层次组件通信

如果你使用 16.3 以上版本的话,对于这种情况可以使用 Context API。

// 创建 Context,可以在开始就传入值
const StateContext = React.createContext()
class Parent extends React.Component {
  render () {
    return (
      // value 就是传入 Context 中的值
      <StateContext.Provider value='yck'>
        <Child />
      </StateContext.Provider>
    )
  }
}
class Child extends React.Component {
  render () {
    return (
      <ThemeContext.Consumer>
        // 取出值
        {context => (
          name is { context }
        )}
      </ThemeContext.Consumer>
    )  
  }
}

(四) 高阶组件(HOC)

高阶组件:实现一个函数,传入一个组件,然后在函数内部再实现一个函数去扩展传入的组件,最后返回一个新的组件。

function withLog (fn) {
    function wrapper(a, b) {
        const result = fn(a, b)
        console.log(result)
        return result
    }
    return wrapper
}
const withLogAdd = withLog(add)
withLogAdd(1, 2)

高阶组件 和 mixin

(1) mixin 隐含了一些依赖,比如我在组件中写了某个 state 并且在 mixin 中使用了,就这存在了一个依赖关系。万一下次别人要移除它,就得去 mixin 中查找依赖。
(2) 多个 mixin 中可能存在相同命名的函数,同时代码组件中也不能出现相同命名的函数,否则就是重写了。
(3) 雪球效应,虽然我一个组件还是使用着同一个 mixin,但是一个 mixin 会被多个组件使用,可能会存在需求使得 mixin 修改原本的函数或者新增更多的函数,这样可能就会产生一个维护成本。

(五) 事件机制

JSX 上写的事件并没有绑定在对应的真实 DOM 上,而是通过事件代理的方式,将所有的事件都统一绑定在了 document 上。这样的方式不仅减少了内存消耗,还能在组件挂载销毁时统一订阅和移除事件。

冒泡到 document 上的事件也不是原生浏览器事件,而是 React 自己实现的合成事件(SyntheticEvent)。因此我们如果不想要事件冒泡的话,调用 event.stopPropagation 是无效的,而应该调用 event.preventDefault。

合成事件

  • 合成事件首先抹平了浏览器之间的兼容问题,另外这是一个跨浏览器原生事件包装器,赋予了跨浏览器开发的能力。
  • 对于原生浏览器事件来说,浏览器会给监听器创建一个事件对象。如果你有很多的事件监听,那么就需要分配很多的事件对象,造成高额的内存分配问题。但是对于合成事件来说,有一个事件池专门来管理它们的创建和销毁,当事件需要被使用时,就会从池子中复用对象,事件回调结束后,就会销毁事件对象上的属性,从而便于下次复用事件对象。

(六) Hooks

function Example() {
  const [count, setCount] = useState(0)  

  // 相当于 componentDidMount 和 componentDidUpdate:
  useEffect(() => {
    // 使用浏览器的 API 更新页面标题
    document.title = `You clicked ${count} times`  
  })  

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  )  
}

(七) Redux

const Counter = ({ value, onIncrement, onDecrement }) => (
  <div>
  <h1>{value}</h1>
  <button onClick={onIncrement}>+</button>
  <button onClick={onDecrement}>-</button>
  </div>
)  

const reducer = (state = 0, action) => {
  switch (action.type) {
    case 'INCREMENT': return state + 1  
    case 'DECREMENT': return state - 1  
    default: return state  
  }
}  

const store = createStore(reducer)  

const render = () => {
  ReactDOM.render(
    <Counter
      value={store.getState()}
      onIncrement={() => store.dispatch({type: 'INCREMENT'})}
      onDecrement={() => store.dispatch({type: 'DECREMENT'})}
    />,
    document.getElementById('root')
  )  
}  

render()  
store.subscribe(render)  

三、通识

(一) MVC 和 MVVM

1. MVC模式

Model: 模型持有所有的数据、状态和程序逻辑。
View: 负责界面的布局和显示。
Controller: 负责模型和界面之间的交互。

View 传送指令到 Controller,Controller 完成业务逻辑后,要求 Model 改变状态,Model 将新的数据发送到 View,用户得到反馈。所有通信都是单向的。
但是 MVC 有一个巨大的缺陷就是控制器承担的责任太大了,随着项目愈加复杂,控制器中的代码会越来越臃肿,导致出现不利于维护的情况。

MVC模式

2. MVVM模式

Model: 模型持有所有的数据、状态和程序逻辑。
View: 视图负责界面的布局和显示。
ViewModel: 封装展示逻辑与状态。

ViewModel 只关心数据和业务的处理,不关心 View 如何处理数据,在这种情况下,View 和 Model 都可以独立出来,任何一方改变了也不一定需要改变另一方,并且可以将一些可复用的逻辑放在一个 ViewModel 中,让多个 View 复用这个 ViewModel。

MVVM模式 MVVM模式

(二) 虚拟 DOM

Virtual DOM 其实就是一棵以 JavaScript 对象作为基础的树,用对象属性来描述节点,实际上它只是一层对真实 DOM 的抽象。最终可以通过一系列操作使这棵树映射到真实环境上。由于 Virtual DOM 是以 JavaScript 对象为基础而不依赖真实平台环境,所以使它具有了跨平台的能力,比如说浏览器平台、Weex、Node 等。

class VNode {
    constructor (tag, data, children, text, elm) {
      /*当前节点的标签名*/
      this.tag = tag  
      /*当前节点的一些数据信息,比如props、attrs等数据*/
      this.data = data  
      /*当前节点的子节点,是一个数组*/
      this.children = children  
      /*当前节点的文本*/
      this.text = text  
    }
    render() {
      if(this.tag === '#text') {
        this.data.forEach(item => {
          element.setAttribute(key, value)
        })
        return document.createTextNode(this.text)
      }
      let el = document.createElement(this.tag)
      this.children.forEach(vChild => {
        el.appendChild(vChild.render())
      })
      return el
    }
}

比如我目前有这么一个 Vue 组件。

<template>
  <span class="demo">
    This is a span.
  </span>
</template>

看看转换成 VNode 以后的情况。

{
    tag: 'span',
    data: {
        class: 'demo'
    },
    text: undefined,
    children: [
        /* 子节点是一个文本VNode节点 */
        {
            tag: undefined,
            data: undefined,
            text: 'This is a span.',
            children: undefined
        }
    ]
}

(三) patch

深度优先,同层比较。先比孩子,再比自己。

function patch (oldNode, node, parentElm) {
    if (!oldNode) {
        addNodes(parentElm, null, node, 0, node.length - 1);
    } else if (!node) {
        removeNodes(parentElm, oldNode, 0, oldNode.length - 1);
    } else {
        if (sameNode(oldNode, node)) {
            patchNode(oldNode, node);
        } else {
            removeNodes(parentElm, oldNode, 0, oldNode.length - 1);
            addNodes(parentElm, null, node, 0, node.length - 1);
        }
    }
}

1.在 oldVnode 不存在的时候,相当于新的 VNode 替代原本没有的节点,所以直接用 addVnodes 将这些节点批量添加到 parentElm 上。

2.在 vnode 不存在的时候,相当于要把老的节点删除,所以直接使用 removeVnodes 进行批量的节点删除即可。

3.当 oldVNode 与 vnode 都存在的时候,需要判断它们是否属于 sameVnode。如果是则进行patchVnode 操作,否则删除老节点,增加新节点。

sameVnode

只有当 key、 tag、 isComment(是否为注释节点)、 data同时定义(或不定义),同时满足当标签类型为 input 的时候 type 相同即可。

4.updateChildren

(1) 在当新老 VNode 节点都是 isStatic(静态的),并且 key 相同时,只要将 从老 VNode 节点拿过来即可。

(2) 当新 VNode 节点是文本节点的时候,直接用 setTextContent 来设置 text。

(3) updateChildren

  1. 首先我们定义 oldStartIdx、newStartIdx、oldEndIdx 以及 newEndIdx 分别是新老两个 VNode 的两边的索引,同时 oldStartVnode、newStartVnode、oldEndVnode 以及 newEndVnode 分别指向这几个索引对应的 VNode 节点。

  2. 接下来是一个 while 循环,在这过程中,oldStartIdx、newStartIdx、oldEndIdx 以及 newEndIdx 会逐渐向中间靠拢。while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx)

    2.1 oldStartVnode 与 newStartVnode 符合 sameVnode 时,说明老 VNode 节点的头部与新 VNode 节点的头部是相同的 VNode 节点,直接进行 patchVnode,同时 oldStartIdx 与 newStartIdx 向后移动一位。

    2.2 oldEndVnode 与 newEndVnode 符合 sameVnode,也就是两个 VNode 的结尾是相同的 VNode,同样进行 patchVnode 操作并将 oldEndVnode 与 newEndVnode 向前移动一位。

    2.3 oldStartVnode 与 newEndVnode 符合 sameVnode 的时候,也就是老 VNode 节点的头部与新 VNode 节点的尾部是同一节点的时候,将 oldStartVnode.elm 这个节点直接移动到 oldEndVnode.elm 这个节点的后面即可。然后 oldStartIdx 向后移动一位,newEndIdx 向前移动一位。

    2.4 oldEndVnode 与 newStartVnode 符合 sameVnode 时,也就是老 VNode 节点的尾部与新 VNode 节点的头部是同一节点的时候,将 oldEndVnode.elm 插入到 oldStartVnode.elm 前面。同样的,oldEndIdx 向前移动一位,newStartIdx 向后移动一位。

    2.5 如果都不符合,我们可以根据某一个 key 的值,快速地从 oldKeyToIdx 中获取相同 key 的节点的索引 idxInOld,然后找到相同的节点。否则如果找到了节点,同时它符合 sameVnode,则将这两个节点进行 patchVnode,如果不符合 sameVnode,只能创建一个新节点。如果没有找到相同的节点,则通过 createElm 创建一个新节点,并将 newStartIdx 向后移动一位。

  3. 当 while 循环结束以后,如果 oldStartIdx > oldEndIdx,说明老节点比对完了,但是新节点还有多的,需要将新节点插入到真实 DOM 中去,调用 addVnodes 将这些节点插入即可。

  4. 如果满足 newStartIdx > newEndIdx 条件,说明新节点比对完了,老节点还有多,将这些无用的老节点通过 removeVnodes 批量删除即可。

(四) 前端路由

前端路由:监听 URL 的变化,然后匹配路由规则,显示相应的页面。

1. Hash 模式

www.test.com/#/ 就是 Hash URL,当 # 后面的哈希值发生变化时,可以通过 hashchange 事件来监听到 URL 的变化,从而进行跳转页面,并且无论哈希值如何变化,服务端接收到的 URL 请求永远是 www.test.com。

2. History 模式

主要使用 history.pushState 和 history.replaceState 改变 URL。通过 History 模式改变 URL 同样不会引起页面的刷新,只会更新浏览器的历史记录。

3. hash 模式和 history 模式的区别

1.URL

  • Hash 模式只可以更改 # 后面的内容
  • History 模式可以通过 API 设置任意的同源 URL History 模式可以通过 API 添加任意类型的数据到历史记录中,Hash 模式只能更改哈希值

2.兼容性

  • Hash 模式无需后端配置,并且兼容性好
  • History 模式在用户手动输入地址或者刷新页面的时候会发起 URL 请求,后端需要配置 index.html 页面用于匹配不到静态资源的时候。

4. Vue 和 React 的区别

(1) 性能
Vue:一个组件在渲染期间依赖于自动追踪,因此系统知道提前预判哪一个组件需要渲染当组件状态发生改变时。每个组件可以被认为具有自动为你实现shouldComponentUpdate,不需要注意嵌套的组件。
总的来说,这不需要开发人员对整个性能进行优化,允许他们更专注于构建应用程序本身。
React:当组件状态改变时,它会触发整个子组件数重新渲染,以根组件作为渲染基点。为了避免不必要的子组件重新渲染,你需要使用PureComponent或者实现 shouldComponentUpdate。

(2) Templating vs JSX

(3) CSS
Vue:CSS in JS
React:className,styled-components,css-modules

(4) 生态
Vue:用于状态管理和路由(以及其他问题)的配套库全部得到官方支持,并与核心库保持同步。
React:把这些问题留给社区,创造一个更加分散的生态系统。 尽管如此,React的生态系统比Vue更为丰富。