董懂 发布的文章

由于 v-if 在条件为 false 时,会销毁它内部的所有内容,当条件为 true 时,重新渲染内部的内容,所以 v-if 可以用作刷新某些区域用。

例子1

例如,input[type=file] 控件,如果你选择的文件跟上次选择的是同一个,它就不会触发 changeinput 事件,除非你选择一个新的文件。即使大多数情况下,这种情形并不需要特殊处理,因为用户不可能老是选择同一个文件,但总是会遇到特殊的时候。这个时候就可以用 v-if 来销毁原来的 file 控件,并渲染一个新 file,这个新的 file 上面绑定的数据、事件都跟原来一模一样:

<!-- template -->
<input type="file" v-if="someCondition" @change="onChange">

/* Data */
data(){
    return {
        someCondition: true
    }
}

/* Methods */
//file的change事件:
onChange(e){
    //想办法让someCondition经历 false - true 的转换
    this.someCondition = false;
    ...some code
    this.someCondition = true
}

现在,input[type=file] 已经是一个全新的了,即使你选择同一个文件,它也会响应 change 事件。

例子2

element-uitree 控件不是很完善,不提供刷新方法(也许是我没有发现)。如果你修改了某一个子项,然后想要重新加载整个树,就可以使用 v-if 来达到目的。用法跟上面是一样的。

End

在 uni-app 中使用自定义 tabBar,需要注意的一共有 3 点:app.json的配置、custom-tab-bar目录的建立、tab 页中getTabBar方法的调用。

首先根据官方文档,配置app.json。在 uni-app 项目中,app.json对应的是pages.json,每个页面的.json文件,需要写在pages.jsonpages-style段中:

{
    "tabBar": {
        "custom": true,
        "color": "#000000",
        "selectedColor": "#000000",
        "backgroundColor": "#000000",
        "list": [
            {
               "pagePath": "pages/index/index",
                "text": "首页"
            },
            {
                "pagePath": "pages/me/index",
                "text": "我的"
            }
        ]
    },
    "pages": [
        {
            "path": "pages/index/index",
            "style": {
                "navigationBarTitleText": "首页",
                "usingComponents": {}
            }
        },
        {
            "path": "pages/me/index",
           "style": {
                "navigationBarTitleText": "我的",
                "usingComponents": {}
            }
        }
    ],
    ......
}

然后需要在根目录下新建custom-tab-bar目录,在这个目录中手动新建小程序四件套,即index.wxmlindex.jsindex.wxssindex.json。这个文件夹会被原封不动地复制到编译好的项目中。接着就是编写 tabBar 的样式和逻辑,这和原生开发是一样的,这里不再赘述。

最后需要在 tab 页面中调用getTabBar方法。uni-app 的页面是.vue文件。在onShow事件中,添加如下代码:

onShow() {
    this.$mp.page.getTabBar().setData({
        selectdIndex: 1
    });
}

注意,getTabBar方法并不是直接在this上,而是在this.$mp.page上。

还有一点需要注意的是,tabBar 会占用一部分页面高度,tab 页最下方的内容会被遮住,需要处理一下这个问题,比如给 tab 页增加一个与 tabBar 高度一样的padding-bottom

Vuex 方便了管理状态,但也使开发流程更加复杂。为了提高开发速度,“让你少按几次键”,Vuex 提供了诸如mapState方法,但其实帮助不大。如果你在 methods 中看到调用了一个陌生的方法,那十有八九是 Vuex 中的 Action 或者 Mutation。

要是你想使用 v-model 绑定 state 中的字段,享受双向绑定的便捷,那在享受之前,你要好好忍受一下折磨,因为要实现这个功能是很麻烦的:在定义了 state 后,还要定义对应的 mutation;导入到组件的 computed 时,不能使用 mapState,还要给导入的 state 设置 setter,在 setter 中 commit mutation。这是至关重要的一步,否则 v-model 不会正常工作,而是会警告你缺少 setter:

Computed property "xxxx" was assigned to but it has no setter

这实在是很麻烦,必须找到更快捷的方法,更愉快地使用 v-model。这里提供一个简单的方法,应该会提高一点点开发速度。

1、定义通用的 mutation

如果给每一个 state 都定义相应的 mutation,那代码就要写到天荒地老。不妨定义一个通用的 mutation,只需传入需要修改的 state 的字段名和新值:

updateState(state, payload){
    state[payload.key] = payload.val;
}

这样就方便多了。至于一些需要特殊处理的 state,再单独给它们定义 mutation。

2、生成带 setter 的 computed

能用程序生成的,怎么能复制粘贴修改呢?computed 是一个对象,咱们就生成一个对象,再用...操作符合并到组件的 computed 中。

首先在你喜欢的地方新建一个 .js 文件。因为提交 mutation 需要用到 store 实例,store 实例又在 Vue 实例上,咱们就从 main.js 或任何你能想到的地方导出 Vue 实例,在刚才新建的 .js 文件中导入:

import vm from '@/main'

接着,定义一个数组,元素是 state 的字段名:

let stateKeys = ['name', 'age', 'sex'];

接着,循环这个数组,生成计算属性:

let _state = {};
stateKeys.map(key =>{
    _state[key] = {
        get(){
            //如果你的state在模块中,记得在`state`后加上.模块名,下同
            return vm.$store.state[key];
        },
        set(val){
            vm.$store.commit('updateState', {
                key,
                val
            });
        }
    }
})

export const state = _state;

之后,在组件中引入刚刚的 state:

import state from '刚刚的js路径和名称'

最后,在组件的 computed 属性中导入 state:

computed: {
    ...state
}

现在,就能在模板中正常使用 state 了:

<input v-model="name">

最后

一个人不喜欢刷碗不一定是个懒人,他可能只是不喜欢干类似的活。程序员一般都不喜欢干复制粘贴这样的重复性工作 :)

End

我是菜鸟,今天才发现这个问题。

Vue计算属性看似是一个函数,但实际上跟 data 更像。如果计算属性返回的是一个对象,并且在模板中读取了这个对象的属性,那就要保证开始时这个对象的属性一定要存在,下面是有问题的代码:

<header>{{ pageInfo.title }}</header>

...
computed: {
    pageInfo(){
        const mapPageInfo = {
            'index': {
                title: 'index page',
                subTitle: 'index subtitle'
            },
            'list': {
                title: 'list page',
                subTitle: 'list subtitle'
            }
        };

        return mapPageInfo[this.$route.path]
    }
}
....

上面的代码看起来没有问题,运行起来也是正常的,但实际上在控制台报错了:

cannot read property title of undefined...

原因就是 pageInfo 在刚开始的时候没有组件中读取的 title 属性。下面是正确的做法:

...
pageInfo(){
    //**保证属性一定是存在的**
    let pageInfo = {
        title: '',
        subTitle: ''
    };
    const mapPageInfo = {
        ...
    }
    ....
    return Object.assign(pageInfo, mapPageInfo[this.$route.path]);
}
...

End

wx.onAppRoute已经失效

最近试图使用wx.onAppRoute来设置一些全局的东西,比如给每个页面都设置onShareAppMessage方法,让每个页面都可以分享:

//app.js
onLaunch(){
    wx.onAppRoute(()=>{
        const pages = getCurrentPages();
        const currPage = pages[pages.length - 1];

        if (currPage.onShareAppMessage !== undefined) return;

        currPage.onShareAppMessage = ()=>{
            return { title: 'Hi' }
        }
    })
}

这样的做法已经失效了,点击右上角的三个点,分享按钮还是灰色的。
难道是我使用的方法不对吗?如果有错误请告诉我~

还可以使用behaviors

幸运的是,现在Page也支持behaviors了,虽然还是要每个页面都引用定义的behaviors,但是总比每个页面都写上一段相同的代码好一点。

End

对象和数组

利用Object()方法来判断一个变量是否为对象:

如果Object方法的参数是一个对象,它总是返回该对象,即不用转换。这一点可以用来判断变量是都是对象。

var obj = {};
var isObject = obj === Object(obj);

利用Object.prototype.toString()方法判断变量类型

toString方法返回变量的类型字符串,可以用来判断变量类型。由于数组、字符串、函数、Date等对象(构造函数)都自定义了自己的toString方法,所以要使用Object.prototype.toString()

Object.prototype.toString.call(2); // "[object Number]"
Object.prototype.toString.call(''); // "[object String]"
Object.prototype.toString.call(true); // "[object Boolean]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(Math); // "[object Math]"
Object.prototype.toString.call({}); // "[object Object]"
Object.prototype.toString.call([]); // "[object Array]"

这个方法比typeof更准确,例如:

typeof []; //'object'

利用Array.isArray()判断变量是否为数组

同样的,这个方法也可以弥补typeof的不足:

typeof []; // 'object'
Array.isArray([]); // true

关于数组的实例方法concat()

“如果数组成员包括对象,concat方法返回当前数组的一个浅拷贝。所谓“浅拷贝”,指的是新数组拷贝的是对象的引用。”
https://wangdoc.com/javascript/stdlib/array.html

确切的意思是,如果数组成员包括对象,concat方法返回的新数组中,对象成员是原数组对象成员的引用:

var arr = [1, 2, {a: 3}];
var arr1 = arr.concat(4); // [1, 2, {a: 3}, 4]
console.log(arr1[2]); // {a: 3}
arr1[2].a = 5;
console.log(arr[2]);  // {a: 5}

会改变原数组的方法

pop push shift unshift reverse splice, sort

slice方法

1、不传参数时,会返回原数组的拷贝

[1, 2, 3].slice(); // 返回原数组的拷贝 [1, 2, 3]

同样的,对于包含对象成员的数组,返回的新数组中的对象仍然是原数组的浅拷贝,也就是引用。

2、可以将类数组对象转换成整整的数组

var arrLikeObj = { 0: 1, 1: 2, length: 2};
Array.prototype.slice.call(arrLikeObj); // [1, 2]

类数组对象,就是具有数字类型的键值,又具有length属性的对象。

利用splice方法插入新元素

splice方法的第二个参数是0时,表示只插入新元素,不删除任何元素:

var arr = [1, 2, 3];
arr.splice(0, 0, 0);
console.log(arr); // [0, 1, 2, 3]

众所周知,在 html 中,下面的代码:

<div style="height: 100px; padding: 20px">
    <div class="inner" style="height: 100%"></div>
</div>

.inner的高度是100px。如果给外面的 div 设置 box-sizing: border-box,那么.inner的高度将会变成100px - 20px - 20px,也就是60px

但是在微信小程序的 scroll-view 组件中却不同:

<scroll-view style="height: 100px; padding: 20px">
    <view class="inner" style="height: 100%"></view>
</scroll-view>

.inner的高度将会是100px + 20px + 20px = 140px。如果给外面的 div 设置 box-sizing: border-box,那么.inner的高度将会变成100px - 20px - 20px + 20px + 20px,也就是100px

也就是说,scroll-view 的高度设置成 100% 后,它会忽略父容器的 padding,始终等于父元素的总高度,设置box-sizing: border-box 也不能够让它表现正常。

所以,如果你想要设置 scroll-view 直接子元素的高度为 100%,那么尽可能使用绝对长度,不要使用 100%。

END

使用 text 组件的时候,不要把代码格式化成下面这样:

<text>
    文字
</text>

这会使得 text 显示多余的空白。
如果你的编辑器一定要像上面那样格式化 text,不能自由配置,那么这里有一个方法,能够避免这个问题,就是:

使用 view 替代 text。

END

现在前端工作已经离不开各种框架了。在各种MVVM框架的基础上,还有各种UI框架。想要什么功能,首先想到的是找找相应的插件,即使那个功能实际上很简单。

这些框架极大的方便了开发,也使更多的人可以从事前端开发工作,而不必有太多的基础知识。

方便的开发牺牲了其他的一些东西。双向数据绑定和虚拟DOM消耗了更多的内存,在移动设备上还体现在消耗更多的电量。快速搭建界面的UI框架生成了非常多的冗余代码,DOM树臃肿不堪。因为不必想方设法地优化DOM结构,不必过于关注JavaScript语言本身,工作过程中创造性的成就感已经非常少,取而代之的是搬砖似的机械重复的劳累感。

硬件的提升给了实现这些东西的基础,硬件提升的性能也往往被软件的升级消耗掉,所以硬件需要不断升级。

其实大多数的情况我们要的就是实现目的,上面这些文字不带有感情色彩。