Open SunXinFei opened 6 years ago
首先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的路由样子为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!!!
Vue包装了数个数组操作函数,使用这些方法操作的数组去,其数据变动时会被vue监测:
vue2.0还增加个方法可以观测Vue.set(items, indexOfItem, newValue) filter(), concat(), slice() 。 this.arr = [];//数组置空 这些不会改变原始数组,但总是返回一个新数组。当使用非变异方法时,可以用新数组替换旧数组 Vue 不能检测以下变动的数组: ① 当你利用索引直接设置一个项时,vm.items[indexOfItem] = newValue ② 当你修改数组的长度时,例如: vm.items.length = newLength
//父组件
<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>
store和state是最基本的概念,VUEX没有做出改变。其实VUEX对整个框架思想并没有任何改变,只是某些内容变化了名称或者叫法,通过改名,以图在一些细节概念上有所区分。
总的来说,VUEX通过弱化概念,在任何东西都没做实质性削减的基础上,使得整套框架更易于理解了。
另外VUEX支持getter,运行中是带缓存的,算是对提升性能方面做了些优化工作,言外之意也是鼓励大家多使用getter。
// 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';
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的回答
Vuex作为vue的插件,利用mixin在组件beforCreated时候,挂载vuex实例,并且会进行parent判断,子组件的$store为parent的$store; Vuex核心是基于vue的computed实现的: 更改数据 mutations->methods 获取数据 getters -> computed 数据 state->data 所以vuex可以说是没有template的vue组件
为了解决不同模块命名冲突的问题,将不同模块的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'
})
需求:当页面加载完毕时,自动获得焦点 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();
})
}
})
写法基本相同,注意一点是,不要绑定匿名函数、箭头函数 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);
}
场景描述:路由中存在动态参数为查询的文本,页面加载完成之后,input框获取url中的参数并显示出来
运行结果:如果是普通的import 路由配置,this.$route.query没有问题可以正确获取url中的参数,但如果是按需加载的路由配置,目前存在这个问题,this.$route.query会在mounted中是空对象,获取url中的参数失败
解决方法:mounted的时候用location.href手动切分字符串或者用正则获取,或者用watch 路由的变化,来给input赋值。
let reg = new RegExp("(^|\\?|&)"+ name +"=([^&]*)(&|$)");
然后获取当前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
}
}
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;
使用babel插件plugin-proposal-do-expressions
我们创建一个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;
}
};
总上所述:
<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>
源码:
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');
}
}
这里面要注意两点:
<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
关于JSX
JSX到JS对象的映射:
JSX 到页面到底经过了这样的过程: 为什么不直接从 JSX 直接渲染构造 DOM 结构,而是要经过中间这么一层呢? 第一:我们拿到一个表示UI的结构对象的时候,不一定渲染到浏览器页面中,比如我可以通过react-canvas渲染到canvas,react-app渲染到原生app中,这个也是react-dom单独抽离,没有和react库在一起的原因。 第二:有了这个对象,当数据变化,需要更新组件的时候,非常方便的比较这个JS对象,而不是直接操作DOM结构,减少浏览器重排,提高性能,这也就是所说的虚拟DOM