- 开始玩玩 vue,当然用 TS
- 在 update 生命周期里要识别是通过 api 改变的数据,
- 解决方式:
- 用 watch 观察数据变动,注意与 react 区别,不用返回新数据直接在节点上改动,也就是 .语法, 只在重新获取数据的时候赋值。
- 通过 vue nextTick 方法(可以在任何地方调用) ,也就是在 dom 全挂载后执行 dom 操作。不然 dom 没有挂载获取不到元素
- 在 render 函数里 , 循环数组 调用 createElement 方法,这样不行,也看不懂是在干嘛?
- 解决方式:
- 通过 h(),调用空的createElement函数来制作空的节点
- render 函数是可以直接返回 VNode[]的,只是不知道为什么类型定义为 VNode
- 可以通过$scopedSlots 里的插槽函数获取插槽
arr = [1, 3, 4];
arr.length = 0; //没有用
obj = [{ name: 1 }, { name: 2 }];
obj[0].name = 3; //直接改
@Component({
props:{
addOrEdit:Boolean,
show:Boolean,
rowData:Object,
}
watch: {
show:function(val){
val && this.initRowData()
}
}
})
class Some extends Vue {
myData:any[];
setRowData(){
if(this.addOrEdit){
// 注意要克隆
// 要是直接 this.myData = this.rowData 会导致在改变this.myData 时改变this.rowData
// 相当于改变了 父组件的某一行记录
this.myData =object.assign({},this.rowData);
}else{ // 新增
this.myData = {name:""}
}
}
}
<div
v-for="menu in list.menuChildList"
:key="menu.code"
:data-code="menu.code"
:class="{active:menu.active}"
class="menu-item menu-child"
@click.capture="navMenuItem" // 注意capture
>
<router-link
:to="'/'+menu.url"
class="nav-item"
:replace="true"
@click="test"
>
{{ menu.name }}
</router-link>
</div>
const props = Vue.extend({
// 在这指定无用
});
@Component({
name: "My",
})
class My extends props {}
<div class="m-child-list">
<ul>
<li
v-for="(item , index) in child"
:key="item[filedObj.id]"
>
<TreePar
v-if="item[filedObj.child].length" // 怎样把 item[filedObj.child].length 作为一个计算属性存起来
:filed-obj="filedObj"
:node="item"
:path="path + '-' + index"
:lev="lev + 1"
/>
<div
v-else
class="m-item"
:data-path="path + '-' + index"
:style="{paddingLeft: (lev+1)*20 + 'px'}"
>
{{ item[filedObj.text] }}-child
</div>
</li>
</ul>
</div>
...
// 怎样取到item 的值作为一个变量 ?
new Vue({
computed:{
isPar: function(){
// item 是 v-for指令里的变量 怎样取到它?
return item[filedObj.child].length
}
}
})
!必须得用 render 来处理吗?
<li
v-for="(item , index) in child"
:key="item[filedObj.id]"
>
{{isPar(item)}}
{{getItem(item)}}
</li>
...
new Vue({
computed:{
isPar: function(item){
// item 是 v-for指令里的变量 怎样取到它?
return function(){
return item[filedObj.child].length
}
}
},
methods:{
getItem: function(item){
return item.some
}
}
})
//计算j属性会缓存,方法每次都会调用
<li
>
{{getItem("e")}}
</li>
...
import Button from "button"
new Vue({
methods:{
getItem: function(item){
return <div>{{item}}<div>
},
getBtn: function(item) {
return <Button>{{item}}</Button>
}
}
})
充分利用组件的 {components:} 来用临时组件,利用 template 里只能出现组件,具体看 tablelist/TBody.vye
<td
v-for="column in fileObj.column"
:key="column.field"
:class="'td-' + column.align"
>
<VNode
:node="node"
:column="column"
:tabField="fileobj.tabField"
:index="index"
/>
</td>
...
@Component({
name: "TBody",
components: {
Button,
VNode: {
functional: true,
render: (h:CreateElement, ctx: RenderContext) => {
const { node, column, tabField, index } = ctx.props
const a = column.formatter ? column.formatter(node, index, tabField) : h("span", node[column.field])
return a
}
}
}
})
//具体看 tablelist/index.vue 和 applicaiton/index.vue 两个页面的引用关系
fileobj = this.init();
mounted ():void {
console.log(this.$slot)
}
init(){
console.log(this.$slot)
}
目解决方法是用$scopedSlots,但是也是在挂载后才能获取到,在使用 slot 时,用具名插槽就把 default 也显示的标记,不然在获取所有的插槽时,会在获取 default 插槽时得到的顺序不一样
- 注意
- 在 在跨级传递时,会有些问题要注意,也就是在“爷父子”,三级组件从子通过父再传给爷时要注意插件对应的关系。通过文件 application/index.vue - tablelist/index.vue - tablelist/TBody.vue 来观察。
- 目前没有想到直接把爷组件 j 具名插槽直接给子组件的方法,得再中间层父组件在命名一具名插槽。因为要具名插槽一一分开传递,并且要带上作用域。
- 在三级组件插槽传递时,在爷组件使用作用域时,发现使用变量的第 3 层属性报错,不知道为什么
// 子组件 TBody
<td
v-for="column in fileObj.column"
:key="column.field"
:class="'td-' + column.align"
>
<slot
:name="column.field"
:node="node"
>
{{ node[column.field] }}
</slot>
</td>
...
// 父组件 Table
<TBody
:table-data="data"
:file-obj="fileObj"
:per-nums="perNums"
:cur-page="curPage"
>
<template
v-for="item in fileObj.column"
v-slot:[item.field]="{node}"
>
<slot
:name="item.field"
:node="node"
/>
</template>
</TBody>
...
// 爷组件
<div class="page-main">
<Table
:data="list"
:no-order="true"
id-field="code"
>
<template #default>
<Column
field="sn"
name="排序"
/>
...
</template>
<template v-slot:url="{node}">
<Column
field="url"
name="URL"
>
{{ node }} //可以
{{ node.ip + "/" + node.homePage }} //报错
</Column>
</template>
...
</Table>
</div>
- 不明白为什么会这样,在爷组件里。作用域 node 返回的也不是字符串就是不能引用里面的属性
当把父组件在的 作用域改为如下就可以用了。正常的没有三级传递的没有这样的情况,看 users/ 下的测试文件
<slot
:name="item.field"
v-bind="node"
/>
- 找到原因了
- 因为涉及到异步请求,在第一次额渲染时,表格的数据为空[],从子作用域传来的数据是没有的是个空对象{},而在爷组件又使用了那些属性,所以报错
- 当涉及到作用域插件的作用域值就得判断,这个值存在不,特别这种异步传递值得
-
在需要对 vue 的 data(也是类的实例属性)初始化时,要是要用一个函数初始化多个属性,本来可以在构造函数 constructor 里来执行,为了不用构造函数,用 vue 的 created 生命周期钩子里初始化,这是周期的第 2 阶段,可以获取属性。第一阶段 beforeCreated 不可以获取属性具体看 combo/DropList.vue
- 注意必须先初始化值,让 vue 对据据进行 observe,否则数据不会与 ui 进行绑定渲染
class DropList extends Vue {
...
listData: node<activeStatus>[] = []; // 注意必须先初始化值,让vue对据据进行 observe
singleClickPre = ""
created ():void{
const { defaultVal } = this.filedObj
const prop = this.getProp()
const obj = formatterListData(prop as drop<"list">, defaultVal)
this.listData = obj.data
this.singleClickPre = obj.singleClickPre
this.bindMethod<"click">(this.clickItem, "click")
}
}
class Combo extends Vue {
...
selected:ISelected[] = [{ id: "1", text: "3333" }]
created ():void {
if (this.bindComMethods) {
this.bindComMethods(this.exportMethods)
}
}
// 暴露给外面的方法
exportMethods:comboMethods = {
// getSelected: this.getSelected
getSelected: () => { // 要是直接在这写返回函数是不会把 selected 的值动态返回出去,只会返回初始值,但是正常返回了this的上下文Combo,
console.log(this.drop)
return this.selected.slice()
}
}
getSelected (): ISelected[] {
return this.selected.map(val => val)
}
}
- 注意框加的数据的 observer 原理
type treeNode<a, p extends string = "children"> = {
[k in p]: treeNode<a, p>[];
} &
anyObj<{
active: a;
expand?: boolean;
}>;
export type filedObj<P extends keyof IDrop, c extends string = "children"> = {
idField: string;
textField: string;
multiply?: boolean;
itemIcon?: string;
defaultVal: string;
field: string;
clickOrCheckForbid?: (
node: anyObj,
field: string,
selectedArr: ISelected[]
) => boolean;
} & IDrop<c>[P];
- 发现了在 ComTree 里总是更新不了数据的原因了,发现在通过 prop 传过来的属性 data 来构造自己的 treeData 时,没有进行深复制,JSON.parse(JSON.stringify()),
- 导致用 . 语法时,总是不能更新 dom,即使数据更新了,dom 还是没变。但是也发现了一个有趣现象。改变某个值的类型为 string 、number时,可以更新到 dom 上,不管其层级深度。但是 Object 和 boolean 的类型不行。
// 当直接使用 props的data来作为 自己的data时。
class ComBoTree {
@Prop() data:any[];
treeData = data
...
expandToggle (index:string):void {
const { filedObj: { childField } } = this
const indexArr = index.split(",").join(`,${childField},`).split(",")
const node = this.treeData[0]
// node[childField][0].name = new Date().getTime() // 会更新
// node.expand = !node.expand // 不会更新
// this.treeData[0].expand = !node.expand // 不会更新
// this.treeData[0] = Object.assign({}, node) // 不能更新
// this.$set(this.treeData, 0, node) // 子组件 ParItem 不会更新
this.$set(this.treeData, 0, Object.assign({}, node)) // 会更新
}
...
}
- 当把 data 的数据进行深复制,也就是不与 props 里的值发生关联是,不管其层级,都可以用 . 语法的
-
-
- 标签用 js 的写法代替
class Combo extends Vue { ... $scopedSlots!:{ inp?:NormalizedScopedSlot, } render () { const { inp } = this.$scopedSlots // inp({}) 是用来传入作用域参数,返回一个Vnode return ( ... <ComboInp drop={drop} tit={tit} selected={selected} slideFn={slideDrop} noicon={noicon} ableClear={ableClear} noRequire={noRequire} clearFn={exportMethods.click} > { inp ? inp({ selected }) : undefined } </ComboInp> ... ) } }
-
- 写在子标签内会总是在 render 时,更新实际运用 插槽的 所有 DropItem 子组件,不管数据有没变动
-
class Combo extends Vue {
...
$scopedSlots!:{
inp?:NormalizedScopedSlot,
}
dropScoped = {
inp: (props) => {
const { itemNode } = props
return (<div>作用域: {itmeNode.text}</div>)
}
}
render () {
const { inp } = this.$scopedSlots // inp({}) 是用来传入作用域参数,返回一个Vnode
return (
...
<div
class={"m-drop " + this.directionUpName + activeName}
data-event={eventKey}
style={{ width: dropWidth ? dropWidth + "px" : undefined }}
>
<SlideBox slide={drop} directionUp={directionUp}>
// 直接写子元素作 为 slot
<Drop
data={data}
initSelect={this.initSelect}
initComboVal={initComboVal}
selected={selected}
changeSelect={changeSelect}
bindMethod={this.bindFn}
maxHeight={this.maxHeight}
>
<div>asdf</div> or asdfasd // 不管是元素还是 字符串 都会导致全部刷新
</Drop>
// 把用函数当子元素,收传过来的作用域,
<Drop
data={data}
initSelect={this.initSelect}
initComboVal={initComboVal}
selected={selected}
changeSelect={changeSelect}
bindMethod={this.bindFn}
maxHeight={this.maxHeight}
>
{ function(props) { // 还是会导致刷新
const { itemNode } = prpos
return inp ? inp({itemNode}) : undefined
}
}
</Drop>
// 自己声明一个 scoped 对象,用来传递,还是会刷新
<Drop
data={data}
initSelect={this.initSelect}
initComboVal={initComboVal}
selected={selected}
changeSelect={changeSelect}
bindMethod={this.bindFn}
maxHeight={this.maxHeight}
scopedSlots={this.dropScoped} // 要理解 这jsx元素 和 vue里元素不是完全一样的 特别是属性这一块 和vue指令
/>
// 直接把上级传过来的 scopeSlots 传过去,这样就不会刷新
<Drop
data={data}
initSelect={this.initSelect}
initComboVal={initComboVal}
selected={selected}
changeSelect={changeSelect}
bindMethod={this.bindFn}
maxHeight={this.maxHeight}
scopedSlots={this.scopedSlots} // 要理解 这jsx元素 和 vue里元素不是完全一样的 特别是属性这一块 和vue指令
/>
</SlideBox>
</div>
...
)
}
}
// scopedSlots 会被转化为如下,而不是属性。所以当在vue的 template里使用这样的属性时。是没有用的。如同在jsx里使用 v-for 等指令是不对的
createElement(Drop, {
attrs: {
data: data,
initSelect: this.initSelect,
initComboVal: initComboVal,
selected: selected,
changeSelect: changeSelect,
bindMethod: this.bindFn,
maxHeight: this.maxHeight
},
scopedSlots: this.$scopedSlots
})
- 为什么会总是更新全部的数据?
- 如果每次 renden 传给 scopedSlots 都是一个新对象,那肯定会全部更新。包括给直接写子元素(会转化为一个createElement函数的调用,相当于每次render都是新的。自己声明的不行,是因为,自己声明的对象没经经过vue内部的方法转化和观察,会先转化,于是生成新的对象。而直接把上级的scopedSlots传过就j避免了每次生成新对象的问题)
<template>
<div><slot></slot> //此处修改为slot</div>
</template>
<script>
export default {
methods: {
renderChild() {
return <div>Child</div>; //在vue jsx插件 3.4.0 之后, h 会自动注入
},
},
created() {
this.$slots.default = [this.renderChild()]; //其实原理就是手动给slot赋值。
},
};
</script>
- ####父组j监听子组件的生命周期,当子组件在生命周期有变动时,执行某些操作,比如 滚动条组件,在包裹的子组件内容变化时,变多或变少,重j计算高度问题。但是slot 插槽用这个方j监听到。react有没这种?
<template>
<ComboList
:data="data"
field="test2"
:width="200"
default-val="3,4"
:multiply="true"
:direction-up="true"
:able-clear="true"
:init-combo-val="init"
@hook:updated="test"
/>
</template>
- 改变User组件的test数据时,ScrollBox也会更新,即使没有传入任何数据给他,他自己的内部的数据也没有变化。
- 其实User每次 render ScrollBox的子组件都会重新生成。这点和React一,但是字符串和数字Vue也是重新生成对象,React这点不是。
<template>
<div class="role-page">
<div>
<Button :handle="click3">
data {{ data.length }}
</Button>
<Button :handle="click2">
test {{ test }}
</Button>
</div>
<div class="test-scroll">
<ScrollBox>
wer
or
<div>测试</div>
or
<div class="tab-list">
<div
v-for="val in data"
:key="val.id"
class="item"
>
<b>{{ val.id + 1 }}</b> <span>{{ val.text }}</span>
</div>
</div>
</ScrollBox>
</div>
</div>
</template>
<script lang="ts">
export default class User extends Vue {
data = [
{ id: 1, text: "er1" },
{ id: 2, text: "er2" },
]
test = 0
updated ():void {
console.log("update Root")
}
click3 ():void {
this.data = new Array(Math.floor(Math.random() * 10000)).fill("").map((val, index) => {
return {
id: index,
text: Math.floor(Math.random() * 30) * index
}
})
}
click2 ():void {
this.test++
}
}
</script>
<template>
<div>{{ data }}</div>
</template>
<script>
const data = "test" // 要把这个数据给tempalte里使用,必须让它成为类成员吗?
export default {
methods: {
renderChild() {
return <div>Child</div>; //在vue jsx插件 3.4.0 之后, h 会自动注入
},
},
data:{
},
created() {
this.$slots.default = [this.renderChild()]; //其实原理就是手动给slot赋值。
},
};
</script>
-
- 通过表格的全选和单选,涉及的计算属性和对应的渲染策略,让我更觉得vue的模式和用法好神奇, 特别是那个 【allChecked】属性。不用显示的直接去改变它,只要改动了它所依赖的属性的值,它就会变化
- 当第一次发现vue没有做事件代理时,觉得好愚蠢。同时生气几乎在所有的for循环的字节的里绑定了事件,也就是说我这样给所有的真实的dom绑定了事件。要是不该,我太难忍了。只用自己做事件代理了。我还去特地看了看react的,它是事件代理,但也给每个dom绑定了事件,不过这个直接的事件不是正式触发行为的事件,正式的事件是在document里,所以删掉也不影响。
- 当在vue的组件里做了事件代理后,发现组件简洁了,不用给子组件传递很多东西。组件交互可以就放在最外层的组件上,可以让逻辑集中,子组件更专注于ui渲染。