Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vue与React #1

Open
SunXinFei opened this issue Mar 9, 2018 · 13 comments
Open

Vue与React #1

SunXinFei opened this issue Mar 9, 2018 · 13 comments
Labels

Comments

@SunXinFei
Copy link
Owner

SunXinFei commented Mar 9, 2018

关于JSX

JSX到JS对象的映射:

<div class='box' id='content'>
  <div class='title'>Hello</div>
  <button>Click</button>
</div>
{
  tag: 'div',
  attrs: { className: 'box', id: 'content'},
  children: [
    {
      tag: 'div',
      arrts: { className: 'title' },
      children: ['Hello']
    },
    {
      tag: 'button',
      attrs: null,
      children: ['Click']
    }
  ]
}

JSX 到页面到底经过了这样的过程:
image
为什么不直接从 JSX 直接渲染构造 DOM 结构,而是要经过中间这么一层呢?
第一:我们拿到一个表示UI的结构对象的时候,不一定渲染到浏览器页面中,比如我可以通过react-canvas渲染到canvas,react-app渲染到原生app中,这个也是react-dom单独抽离,没有和react库在一起的原因。
第二:有了这个对象,当数据变化,需要更新组件的时候,非常方便的比较这个JS对象,而不是直接操作DOM结构,减少浏览器重排,提高性能,这也就是所说的虚拟DOM

@SunXinFei SunXinFei added the ECMA6 label Mar 9, 2018
@SunXinFei SunXinFei reopened this Aug 15, 2018
@SunXinFei SunXinFei changed the title ECMA6中的一些总结 React与ECMA6中的一些总结 Aug 31, 2018
@SunXinFei
Copy link
Owner Author

SunXinFei commented Aug 31, 2018

关于vue中route路由中的的设置和获取并且多参数

使用query设置和获取

首先query的路由样子为http://localhost:8080/#/feed?search=123&type=2
路由的配置:

{
      path: "/feed",
      name: "feed",
      component: Feed
}

路由的获取:

this.$route.query.search

路由的跳转:

this.$router.push({ path: "/feed", query:{ search:123, type:2 } });

使用params设置和获取

首先params的路由样子为http://localhost:8080/#/detail/1234/2333
路由的配置

{
      path: '/detail/:feedId/:detailId',//如果feedId为非必需的参数,则后面加问号为path: '/detail/:feedId?'
      name:'detail',
      component: Detail
}

路由的获取

this.$route.params. feedId

路由的跳转

this.$router.push({ name: "detail", params: { feedId: feedId, detailId: detailId } });

> 注意:params传参,push里面只能是 name:'xxxx',不能是path:'/xxx',
> 因为params只能用name来引入路由,如果这里写成了path,接收参数页面会是undefined!!!

@SunXinFei SunXinFei changed the title React与ECMA6中的一些总结 Vue与React与ECMA6中的一些总结 Sep 17, 2018
@SunXinFei
Copy link
Owner Author

SunXinFei commented Sep 17, 2018

Vue中的数据操作

Vue包装了数个数组操作函数,使用这些方法操作的数组去,其数据变动时会被vue监测:

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

vue2.0还增加个方法可以观测Vue.set(items, indexOfItem, newValue)
filter(), concat(), slice() 。
this.arr = [];//数组置空
这些不会改变原始数组,但总是返回一个新数组。当使用非变异方法时,可以用新数组替换旧数组
Vue 不能检测以下变动的数组:
① 当你利用索引直接设置一个项时,vm.items[indexOfItem] = newValue
② 当你修改数组的长度时,例如: vm.items.length = newLength

Vue中自定义组件双向绑定

//父组件
<template>
      <abcde v-model="text3" />
      <p>{{String(text3)}}</p>
</template>
<script>
export default {
  data(){
  return{
   text3 = 233
  }
}
</script>
//子组件abcde
<template>
  <div>
    <button @click="plus">加</button>
    <button @click="minus">减</button>
    <button @click="equal">赋值</button>
  </div>
</template>

<script>
export default {
  props:['value'],
  computed:{
    ccc:{
      get:function(){
        return this.value;
      },
      set:function(v){
        this.$emit('input',v)
      }
    }
  },
  methods:{
    plus(){
      this.ccc++;
    },
    minus(){
      this.ccc--;
    },
    equal(){
      this.ccc = 250
    }
  }
}
</script>

参考:自定义组件的 v-model
1569182804

@SunXinFei
Copy link
Owner Author

Redux vs VUEX 对比分析

store和state是最基本的概念,VUEX没有做出改变。其实VUEX对整个框架思想并没有任何改变,只是某些内容变化了名称或者叫法,通过改名,以图在一些细节概念上有所区分。

  • VUEX弱化了dispatch的存在感。VUEX认为状态变更的触发是一次“提交”而已,而调用方式则是框架提供一个提交的commit API接口。
  • VUEX取消了Redux中Action的概念。不同于Redux认为状态变更必须是由一次"行为"触发,VUEX仅仅认为在任何时候触发状态变化只需要进行mutation即可。Redux的Action必须是一个对象,而VUEX认为只要传递必要的参数即可,形式不做要求。
  • VUEX也弱化了Redux中的reducer的概念。reducer在计算机领域语义应该是"规约",在这里意思应该是根据旧的state和Action的传入参数,"规约"出新的state。在VUEX中,对应的是mutation,即"转变",只是根据入参对旧state进行"转变"而已。

总的来说,VUEX通过弱化概念,在任何东西都没做实质性削减的基础上,使得整套框架更易于理解了。

另外VUEX支持getter,运行中是带缓存的,算是对提升性能方面做了些优化工作,言外之意也是鼓励大家多使用getter。

@SunXinFei
Copy link
Owner Author

Redux中action重名的问题解决方法

// a.js
export const CLEARDATA= 'CLEARDATA';
// b.js
export const CLEARDATA= 'CLEARDATA';

解决方法
1.可以直接使用ES6的Symbol对象,可以保证全局唯一性。

// a.js
export const CLEARDATA= Symbol('CLEARDATA');
// b.js
export const CLEARDATA= Symbol('CLEARDATA');

2.命名时加入“名空间”限制

// a.js
export const CLEARDATA= 'a/CLEARDATA';
// b.js
export const CLEARDATA= 'b/CLEARDATA';

@SunXinFei
Copy link
Owner Author

关于React中的super参数props

constructor() {
    super()
    console.log(this.props)//undefined
    console.log(props)//error
}
constructor(props) {
    super()
    console.log(this.props)//undefined
    console.log(props)//{ icon: 'home', … }
 }
 constructor(props) {
    super(props)
    console.log(this.props)//{ icon: 'home', … }
    console.log(props)//{ icon: 'home', … }
}

所以在子类组件的构造函数constructor和super中加入参数props,就是为了让子类可以访问this.props,
stackoverflow的回答

@SunXinFei
Copy link
Owner Author

SunXinFei commented Sep 25, 2018

Vuex原理简述

image
Vuex作为vue的插件,利用mixin在组件beforCreated时候,挂载vuex实例,并且会进行parent判断,子组件的$store为parent的$store;
Vuex核心是基于vue的computed实现的:
更改数据 mutations->methods
获取数据 getters -> computed
数据 state->data
所以vuex可以说是没有template的vue组件
image

Vuex模块化,解决不同模块命名冲突的问题

为了解决不同模块命名冲突的问题,将不同模块的namespaced:true,之后在不同页面中引入getter、actions、mutations时,需要加上所属的模块名

/*store/modules/Kanban.js */
const state = {
    labelList: []
};

const getters = {
    labelList: state => state.labelList
};

const actions = {
    setLabelList({ commit, state }, data) {
        commit(`SET_LABEL_LIST`, data);
    },
};

const mutations = {
    SET_LABEL_LIST(state, data) {
        state.labelList = data;
    }
};

export default {
    namespaced:true, //只需添加这一句即可,其他代码不变
    state,
    mutations,
    actions,
    getters
}
/*store/modules/index.js */
/**
 * The file enables `@/store/index.js` to import all vuex modules
 * in a one-shot manner. There should not be any reason to edit this file.
 */

const files = require.context('.', false, /\.js$/)
const modules = {}

files.keys().forEach(key => {
  if (key === './index.js') return
  modules[key.replace(/(\.\/|\.js)/g, '')] = files(key).default
})

export default modules
/*store/index.js*/
import Vue from 'vue'
import Vuex from 'vuex'

import modules from './modules'

Vue.use(Vuex)

export default new Vuex.Store({
  modules,
  strict: process.env.NODE_ENV !== 'production'
})

image

vue中keep-alive原理

  • 获取keep-alive下第一个子组件实例对象,通过他来获取组件名
  • 通过组件名去匹配include和exclude,判断当前组件是否需要缓存,如果不需要缓存则直接返回当前组件实例的Vnode
  • 需要缓存,会判断cache中是否已经存在,如果存在,则将它原来位置上key移除,LRU
  • 不存在,则进行max范围判断,消减未使用时间最长的组件key
  • 将组件keepAlive设置为true

@SunXinFei
Copy link
Owner Author

SunXinFei commented Oct 9, 2018

vue 页面加载完成,input自动获得焦点

需求:当页面加载完毕时,自动获得焦点
1.如果使用的是原生的input,只需在mouned生命函数中添加focus即可

 <input ref="aaa" value="123123"/>
var app = new Vue({
  el: '#app',
  mounted(){
        this.$refs.aaa.focus();
       //这里光标会在文本最前方,需要单独写方法,移动到文本最末尾
      moveToEnd(this.$refs.aaa);
       //选中框内文本
      this.$refs.aaa.select();
  }
})
//控制光标移动到末尾的方法
function moveToEnd(el) {
    if (typeof el.selectionStart == "number") {
        el.selectionStart = el.selectionEnd = el.value.length;
    } else if (typeof el.createTextRange != "undefined") {
        el.focus();
        var range = el.createTextRange();
        range.collapse(false);
        range.select();
    }
} 

2.如果使用了非原生的组件,比如iView的Input,就需要在mounted中加入nextTick

<i-input ref="aaa" value="123123"/>
var app = new Vue({
  el: '#app',
  mounted(){
  	this.$nextTick(() => {
        this.$refs.aaa.focus();
       //选中所有的文本,即需要找到dom元素
      this.$refs.aaa.refs.input.select();
    })		
  }
})

@SunXinFei SunXinFei changed the title Vue与React与ECMA6中的一些总结 Vue与React Oct 10, 2018
@SunXinFei
Copy link
Owner Author

vue与react绑定事件

写法基本相同,注意一点是,不要绑定匿名函数、箭头函数
vue的写法:

mounted(){
   window.addEventListener('resize', this.handleLoad)
},
destroyed(){
    window.removeEventListener('resize', this.handleLoad)
}

如果vue的组件是keep-live则不能在mounted和destroyed里面写了,改为activated、deactivated
react的写法:

componentDidMount() {
    window.addEventListener('resize', this.handleLoad);
}
componentWillUnmount() {
    window.removeEventListener('resize', this.handleLoad);
}

@SunXinFei
Copy link
Owner Author

SunXinFei commented Feb 20, 2019

vue的按需加载路由配置引发的问题

场景描述:路由中存在动态参数为查询的文本,页面加载完成之后,input框获取url中的参数并显示出来
运行结果:如果是普通的import 路由配置,this.$route.query没有问题可以正确获取url中的参数,但如果是按需加载的路由配置,目前存在这个问题,this.$route.query会在mounted中是空对象,获取url中的参数失败
解决方法:mounted的时候用location.href手动切分字符串或者用正则获取,或者用watch 路由的变化,来给input赋值。
let reg = new RegExp("(^|\\?|&)"+ name +"=([^&]*)(&|$)");

vue路由守卫

vue的provide

然后获取当前inject 选项中的所有key,然后遍历每一个key,拿到每一个key的from属性记作provideKey,provideKey就是上游父级组件提供的源属性,然后开启一个while循环,从当前组件起,不断的向上游父级组件的_provided属性中(父级组件使用provide选项注入数据时会将注入的数据存入自己的实例的_provided属性中)查找,直到查找到源属性的对应的值,将其存入result中,如下:

for (let i = 0; i < keys.length; i++) {
  const key = keys[i]
  const provideKey = inject[key].from
  let source = vm
  while (source) {
    if (source._provided && hasOwn(source._provided, provideKey)) {
      result[key] = source._provided[provideKey]
      break
    }
    source = source.$parent
  }
}

@SunXinFei
Copy link
Owner Author

React设置默认props值和类型判定

import React, {Component} from 'react';
import PropTypes from 'prop-types';

const defaultProps = {
name: '',
age: 0,
sex: '未知',
list: []
};

class Test extends Component {
// ...
}

Test.defaultProps = defaultProps; // 设置默认props
// 设置props参数类型
Test.propTypes = {
name: PropTypes.string,
age: PropTypes.number,
sex: PropTypes.string,
list: PropTypes.array
};

export default Test;

@SunXinFei
Copy link
Owner Author

SunXinFei commented Oct 10, 2019

React中jsx支持if else写法

使用babel插件plugin-proposal-do-expressions

Redux的本地持久化存储与获取

我们创建一个utils.js文件,内容如下,这两个方法分别为本地存储(利用store.subscribe监听)和本地读取数据(用来初始化默认redux的state值),这里简单使用localStorage,如果使用第三方存储,只需要替换即可

/**
 * 本地存储常量Key
 */
export const StorageKey = "FILE_MANAGE_STORE"

/**
 * 将Redux中store的变化本地持久化
 * @param {Object} store
 */
export const subscribeRecord = (store) => { // 将状态记录到 localStorage
	store.subscribe(() => {
		let data = store.getState().toJS();
		data = JSON.stringify(data);
		data = encodeURIComponent(data);
		if (window.btoa) {
			data = btoa(data);
		}
		console.log(data, 'subscribeRecord');
		localStorage.setItem(StorageKey, data);
	});
}

/**
 * 将持久化数据读取出来
 */
export const lastRecord = (() => { // 上一把的状态
	let data = localStorage.getItem(StorageKey);
	if (!data) {
		return false;
	}
	try {
		if (window.btoa) {
			data = atob(data);
		}
		data = decodeURIComponent(data);
		data = JSON.parse(data);
	} catch (e) {
		if (window.console || window.console.error) {
			window.console.error('读取记录错误:', e);
		}
		return false;
	}
	console.log(data, 'lastRecordData');
	return data;
})();

在页面初始化的js文件中,给store绑定上subscribe,作为全局监听

import store from './store';
import utils from './utils';

utils.subscribeRecord(store); // 将更新的状态记录到localStorage

jsx页面中的逻辑保持不变,即为正常触发store的action即可,
reducer写法我们要进行改变,我们将判断lastRecord和存储的属性是否存在,来给予defaultState默认值

import * as actionType from '../../actionType';
import { fromJS, List } from 'immutable';
import utils from '../../../utils';

let defaultState = utils.lastRecord && utils.lastRecord.folderPathLocal && utils.lastRecord.folderPathLocal.length !== 0 ?
  List(utils.lastRecord.folderPathLocal) : List([]);

console.log(defaultState.toJS(), 'defaultState', 'folderPathLocal');

export default (state = defaultState, action = {}) => {
  switch (action.type) {
    case actionType.ADDFOLDERPATHFORLOCAL:
      return action.data;
    default:
      return state;
  }
};

总上所述:

  1. 我们在全局利用subscribeRecord方法中的store.subscribe监听store的变化,这样当页面中数据触发redux的action,即会进行本地存储;
  2. 而初始化页面时,我们通过lastRecord方法来获取本地存储数据,并利用defaultState来进行默认值设定,这样再搭配reducers的拆分,我们将分别获得各个reducers的在本地存储的数据,完成页面的数据初始化
  3. 这样有一个特别明显的好处就是不会污染业务代码以及逻辑,也使得一个项目的后期增加本地存储的需求可以实现,只需要添加store监听以及修改reducers的初始化即可
    参考项目:https://github.com/chvin/react-tetris

@SunXinFei
Copy link
Owner Author

SunXinFei commented Jun 23, 2020

自定义元素

<my-element content="Custom Element"> Hello</my-element>
<script>
  class MyElement extends HTMLElement {//自定义元素
    get content() {
      return this.getAttribute('content');
    }
    set content(val) {
      this.setAttribute('content', val);
    }
  }
  //原生的window.customElements对象的define方法用来定义 Custom Element。该方法接受两个参数,第一个参数是自定义元素的名字,第二个参数是一个 ES6 的class。
  window.customElements.define('my-element', MyElement);
  function customTag(tagName, fn) {//Array.from([arguments]);可以将字符串,数组,类数组集合转化为数组
    Array
      .from(document.getElementsByTagName(tagName))
      .forEach(fn);
  }
  function myElementHandler(element) {
    element.textContent = element.content;
  }

  window.onload = function () {//在页面元素加载完之后,才执行
    customTag('my-element', myElementHandler);
  }
</script>

@SunXinFei
Copy link
Owner Author

SunXinFei commented Jun 29, 2020

vue的nextick

源码:

const callbacks = []   // 回调队列
let pending = false    // 异步锁

// 执行队列中的每一个回调
function flushCallbacks () {
    pending = false     // 重置异步锁
    // 防止出现nextTick中包含nextTick时出现问题,在执行回调函数队列前,提前复制备份并清空回调函数队列
    const copies = callbacks.slice(0)
    callbacks.length = 0
    // 执行回调函数队列
    for (let i = 0; i < copies.length; i++) {
        copies[i]()
    }
}

export function nextTick (cb?: Function, ctx?: Object) {
    let _resolve
    // 将回调函数推入回调队列
    callbacks.push(() => {
        if (cb) {
            try {
                cb.call(ctx)
            } catch (e) {
                handleError(e, ctx, 'nextTick')
            }
        } else if (_resolve) {
            _resolve(ctx)
        }
    })
    // 如果异步锁未锁上,锁上异步锁,调用异步函数,准备等同步函数执行完后,就开始执行回调函数队列
    if (!pending) {
        pending = true
        if (useMacroTask) {
            macroTimerFunc()
        } else {
            microTimerFunc()
        }
    }
    // 如果没有提供回调,并且支持Promise,返回一个Promise
    if (!cb && typeof Promise !== 'undefined') {
        return new Promise(resolve => {
            _resolve = resolve
        })
    }
}
Watcher.prototype.update = function update () {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true;
    } else if (this.sync) {
      this.run();
    } else {
      queueWatcher(this);
    }
  };
function queueWatcher (watcher) {
  var id = watcher.id;
  if (has[id] == null) {
    has[id] = true;
    if (!flushing) {
      queue.push(watcher);
    } else {
      // if already flushing, splice the watcher based on its id
      // if already past its id, it will be run next immediately.
      var i = queue.length - 1;
      while (i > index && queue[i].id > watcher.id) {
        i--;
      }
      queue.splice(i + 1, 0, watcher);
    }
    // queue the flush
    if (!waiting) {
      waiting = true;

      if (!config.async) {
        flushSchedulerQueue();
        return
      }
      nextTick(flushSchedulerQueue);
    }
  }
}
function flushSchedulerQueue () {
  flushing = true;
  var watcher, id;

  // Sort queue before flush.
  // This ensures that:
  // 1. Components are updated from parent to child. (because parent is always
  //    created before the child)
  // 2. A component's user watchers are run before its render watcher (because
  //    user watchers are created before the render watcher)
  // 3. If a component is destroyed during a parent component's watcher run,
  //    its watchers can be skipped.
  /*
  给queue排序,这样做可以保证:
  1.组件更新的顺序是从父组件到子组件的顺序,因为父组件总是比子组件先创建。
  2.一个组件的user watchers比render watcher先运行,因为user watchers往往比render watcher更早创建
  3.如果一个组件在父组件watcher运行期间被销毁,它的watcher执行将被跳过。
*/
  queue.sort(function (a, b) { return a.id - b.id; });

  // do not cache length because more watchers might be pushed
  // as we run existing watchers
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index];
    if (watcher.before) {
      watcher.before();
    }
    id = watcher.id;
    has[id] = null;
    watcher.run();    //执行Watcher
    // in dev build, check and stop circular updates.
    if (has[id] != null) {
      circular[id] = (circular[id] || 0) + 1;
      //如果超出最大更新次数,就给个提示。
      if (circular[id] > MAX_UPDATE_COUNT) {
        warn(
          'You may have an infinite update loop ' + (
            watcher.user
              ? ("in watcher with expression \"" + (watcher.expression) + "\"")
              : "in a component render function."
          ),
          watcher.vm
        );
        break
      }
    }
  }

  // keep copies of post queues before resetting state
  var activatedQueue = activatedChildren.slice();
  var updatedQueue = queue.slice();

  //重置队列状态
  resetSchedulerState();

 //调用生命周期钩子
  callActivatedHooks(activatedQueue);
  callUpdatedHooks(updatedQueue);

  // devtool hook
  /* istanbul ignore if */
  if (devtools && config.devtools) {
    devtools.emit('flush');
  }
}

这里面要注意两点:

  1. 响应式数据修改完,会执行wathcer.update,wathcer.update会执行queueWatcher,queueWatcher会执行nextick(flushSchedulerQueue),而flushSchedulerQueue是执行里面首先会对queue排序:父组件放子组件wathcer前面,render watcher放其他watcher后面,然后依次执行所有watcher的run函数。
  2. 用户会主动调用nextTick函数,传入回调函数
    所以nextick函数首先每次调用会把回调函数放到callbacks队列里面,包括用户主动调用的以及flushSchedulerQueue,第一次触发nextick,就会将pending设置为true,这样保证建立一个异步任务,后面执行的nextick都只是维护callback队列(而不是三次调用nextick产生三个异步任务);
    另外执行flushCallbacks的时候,会进行备份回调函数队列,目的是防止nextick回调内再次嵌套nextick,如果 flushCallbacks 不做特殊处理,直接循环执行回调函数,会导致里面nextTick 中的回调函数会进入回调队列。相当于flushCallbacks->异步锁解开->nextick->创建异步任务

vue中静态资源引用(dynamic img src)

<template>
  <div class="demo">
    <!-- 成功引入的三种方法: -->
    <!-- 图1 -->
    <div class="img1"></div>
    <!-- 图2 -->
    <div class="img2" :style="{backgroundImage: 'url(' + bg2 + ')' }"></div>
    <!-- 图3 -->
    <img src="~@/../static/images/logo3.png" width="100">
     <!-- 图4 -->
    <img :src="require(`@/assets/${imgUrl}`)" alt="">
  </div>
</template>

<script>
import Bg2 from '@/../static/images/logo2.png'

export default {
    name: 'App',
    data () {
        return {
            bg2: Bg2,
            imgUrl:'aa.svg'
        }
    }
}
</script>

<style>
    .demo{width: 100px;margin: 50px auto;}
    .img1{
        width: 100px;
        height: 100px;
        background: url('~@/../static/images/logo1.png') center center no-repeat;
        background-size: 100px auto;
    }
    .img2{
        width: 100px;
        height: 100px;
        background-position: center center;
        background-repeat:  no-repeat;
        background-size: 100px auto;
    }
</style>

参考:
vuejs-templates/webpack#450 (comment)

vue组件内methods方法是如何绑定this的

function initMethods (vm: Component) {
  const methods = vm.$options.methods
  if (methods) {
    for (const key in methods) {
      vm[key] = methods[key] == null ? noop : bind(methods[key], vm)
      if (process.env.NODE_ENV !== 'production' && methods[key] == null) {
        warn(
          `method "${key}" has an undefined value in the component definition. ` +
          `Did you reference the function correctly?`,
          vm
        )
      }
    }
  }
}

https://segmentfault.com/q/1010000007225390

Vue3.0

Object.defineProperty -> Proxy

Object.defineProperty只能是通过遍历才能进行数据劫持,以及不能劫持数组事件,需要通过数组方法重写,而Proxy则避免了这几个问题

Virtual DOM 重构

2.0中Vnode的颗粒度是组件,3.0将颗粒度细化,每次触发更新不再以组件为单位进行遍历,而是动态内容的数量相关

增加了类似于react hook的功能

TypeScript

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant