当下公司在做一个媒体门户网站,后台由另一家公司使用java开发并提供接口,本人负责开发后台页面,使用的是vue-element-admin开发
下面说一下问题场景,在开发过程中有一个后台管理员角色页面,其中包含一个表单dialog,在其中使用了el-tree组件,相关 代码结构如下:

<div class="filter-container">
    <el-button class="filter-item" style="margin-left: 10px;" v-waves @click="handleCreate" type="primary" icon="el-icon-edit">新增角色
    </el-button>
</div>
<el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible" width="50%">
  <el-form :rules="rules" ref="dataForm" :model="temp" label-position="top" label-width="90px"
           style='width: 400px; margin-left:50px;'>
      <el-form-item label="选择权限" prop="sysPermission">
          <el-tree ref="tree" :data="sysPermission" :props="formProps" show-checkbox
                   @check-change="handleCheckChange" node-key="id"></el-tree>
      </el-form-item>
  </el-form>
</el-dialog>

相关的js如下:

export default {
        name: 'sysRoleList',
        data() {
            return {
                tableKey: 0,
                list: null,
                total: null,
                listLoading: true,
                formLoading: true,
                listQuery: {
                    page: 1,
                    limit: 20,
                    importance: undefined,
                    title: undefined,
                    type: undefined,
                    sort: '+id'
                },
                dialogFormVisible: false,
                dialogStatus: 'update',
                textMap: {
                    update: '编辑角色',
                    create: '新增角色'
                },
                rules: {
                    sysRoleName: [{required: true, message: '必须填写角色名称', trigger: 'blur'}]
                },
                // 表单数据
                temp: {
                    id: '',
                    sysPermissionList: [],
                    sysRoleName: ''
                },
                currentKeys: [],
                // 表单权限字段映射
                formProps: {
                    label: 'sysPermissionName'
                },
                sysPermission: {}
            }
        },
        created() {
            // 列表数据
            // this.getList()
            // 获取所有权限
            // this.findAllSysPermission()
        },
        methods: {
            /*
            * todo:checkbox状态变更监听
            * */
            handleCheckChange(data, checked, indeterminate) {
                const idObj = {id: data.id}
                this.temp.sysPermissionList.push(idObj)
            },
            resetTemp() {
                this.temp = {
                    id: '',
                    sysRoleName: '',
                    sysPermissionList: []
                }
            },
            handleCreate() {
                this.resetTemp()
                this.dialogStatus = 'create'
                this.currentKeys = []
                this.dialogFormVisible = true
                this.$nextTick(() => {
                    this.$refs['dataForm'].clearValidate()
                    this.$refs.tree.setCheckedKeys(this.currentKeys)
                })
            },
            handleUpdate(row) {
                this.resetTemp()
                this.dialogStatus = 'update'
                this.dialogFormVisible = true
                this.temp = Object.assign({}, row) // copy obj
                this.currentKeys = []
                row.sysPermissionList.forEach((value, index) => {
                    this.currentKeys.push(value[0])
                })
                this.$nextTick(() => {
                    this.$refs['dataForm'].clearValidate()
                    this.$refs.tree.setCheckedKeys(this.currentKeys)
                })
            }
        }
    }

需求:
需要在每次编辑数据的时候触发<el-tree>的方法setCheckedKeys
问题:
之前没有把this.$refs.tree.setCheckedKeys()写在this.$nextTick的callback之中,因此会提示:

TypeError: Cannot read property 'setCheckedKeys' of undefined

解决方法:
把dialog组件内的其他需要执行方法的组件方法写到this.$nextTick的callback之中
原因如下(官方):

可能你还没有注意到,Vue 异步执行 DOM 更新。只要观察到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作上非常重要。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部尝试对异步队列使用原生的 Promise.then 和 MessageChannel,如果执行环境不支持,会采用 setTimeout(fn, 0) 代替。 例如,当你设置 vm.someData = ‘new value’ ,该组件不会立即重新渲染。当刷新队列时,组件会在事件循环队列清空时的下一个“tick”更新。多数情况我们不需要关心这个过程,但是如果你想在 DOM 状态更新后做点什么,这就可能会有些棘手。虽然 Vue.js 通常鼓励开发人员沿着“数据驱动”的方式思考,避免直接接触 DOM,但是有时我们确实要这么做。为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用 Vue.nextTick(callback) 。这样回调函数在 DOM 更新完成后就会调用。
深入响应式原理

个人理解:
vue这么做是因为频繁的更新dom是特别耗费性能的,所以搞了一个批处理更新,把所有的update操作放到任务队列中,等主线程中执行栈的所有同步任务执行完毕,系统就会读取任务队列
一个比较典型的场景,created回调里是无法直接通过this.$refs获取到用ref命名的子组件的,只有通过$nextTick才能访问到。还有比如dialog里有一个步骤条组件,在每次打开对话框都想触发步骤1的动作。如果直接写step=0;step=1;是不会有变化的,因为整个函数执行完之前DOM都不会刷新。把step=1放到$nextTick里就可以了