SunXinFei / sunxinfei.github.io

前后端技术相关笔记,已迁移到 Issues 中
https://github.com/SunXinFei/sunxinfei.github.io/issues
32 stars 3 forks source link

Vue与React #1

Open SunXinFei opened 6 years ago

SunXinFei commented 6 years ago

关于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 commented 5 years ago

关于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 commented 5 years ago

Vue中的数据操作

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

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 commented 5 years ago

Redux vs VUEX 对比分析

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

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

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

SunXinFei commented 5 years ago

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 commented 5 years ago

关于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 commented 5 years ago

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原理

SunXinFei commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

自定义元素

<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 commented 4 years ago

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>
参考:
https://github.com/vuejs-templates/webpack/issues/450#issuecomment-388515010
## 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