Vue类

Vue类

React / Vue 项目时为什么要在列表组件中写 key,其作用是什么?

v-for一般用在列表的渲染,渲染的时候会默认遵守就地复用策略。

就地复用策略:

当 Vue 正在更新使用 v-for 渲染的元素列表时,它默认使用“就地更新”的策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。

这种方式只适用于列表渲染不依赖子组件状态,或临时 DOM 状态变化。

vuereact 都是采用 diff 算法来对比新旧虚拟节点,从而更新节点。

vuediff 函数交叉对比中,当新节点跟旧节点头尾交叉对比没有结果时,会根据新节点的 key 去对比旧节点数组中的 key,从而找到相应旧节点(这里对应的是一个 key => indexmap 映射)。

如果没有找到就认为是一个新增节点。

而如果没有 key,那么就会采用遍历查找的方式去找到对应的旧节点。

种一个 map 映射,另一种是遍历查找。

相比而言,map 映射的速度更快。

为什么 VuexmutationReduxreducer 中不能做异步操作?

Mutation 必须是同步函数

一条重要的原则就是要记住 mutation 必须是同步函数。为什么?请参考下面的例子

mutations: {
  someMutation (state) {
    api.callAsyncMethod(() => {
      state.count++
    })
  }
}

观察 devtool 中的 mutation 日志。每一条 mutation 被记录,devtools 都需要捕捉到前一状态和后一状态的快照。

然而,在上面的例子中 mutation 中的异步函数中的回调让这不可能完成:因为当 mutation 触发的时候,回调函数还没有被调用,devtools 不知道什么时候回调函数实际上被调用——实质上任何在回调函数中进行的状态的改变都是不可追踪的。

区分 actionsmutations 并不是为了解决竞态问题,而是为了能用 devtools 追踪状态变化。

  • 事实上在 vuex 里面 actions 只是一个架构性的概念,并不是必须的,说到底只是一个函数,你在里面想干嘛都可以,只要最后触发 mutation 就行。
  • 异步竞态怎么处理那是用户自己的事情。vuex 真正限制你的只有 mutation 必须是同步的这一点(在 redux 里面就好像 reducer 必须同步返回下一个状态一样)。
  • 同步的意义在于这样每一个 mutation 执行完成后都可以对应到一个新的状态(和 reducer 一样),这样 devtools 就可以打个 snapshot 存下来,然后就可以随便 time-travel 了。
  • 如果你开着 devtool 调用一个异步的 action,你可以清楚地看到它所调用的 mutation 是何时被记录下来的,并且可以立刻查看它们对应的状态。

在此前提下,开发者们总结出:vuex的处理方式是同步在mutation里面,异步在actions里面。

尤雨溪对于actions 和 mutations的区分

Vue 中,子组件为何不可以修改父组件传递的 Prop,如果修改了,Vue 是如何监控到属性的修改并给出警告的。

子组件为何不可以修改父组件传递的 Prop

  • 一个父组件不只有你一个子组件。同样,使用这份prop数据的也不只有你一个组件。如果每个子组件都能修改prop的话,将会导致修改数据的源头不止一处。
  • 单向数据流,易于监测数据的流动。出现了错误可以更加迅速的定位到错误的位置。

如果修改了,Vue是如何监控到属性的修改并给出警告的

// 在initProps的时候,在defineReactive时通过判断是否在开发环境
// 如果是开发环境,会在触发set的时候判断是否此key是否处于updatingChildren中被修改
// 如果不是,说明此修改来自子组件,触发warning提示
if (process.env.NODE_ENV !== "production") {
  var hyphenatedKey = hyphenate(key);
  if (
    isReservedAttribute(hyphenatedKey) ||
    config.isReservedAttr(hyphenatedKey)
  ) {
    warn(
      '"' +
        hyphenatedKey +
        '" is a reserved attribute and cannot be used as component prop.',
      vm
    );
  }
  defineReactive$$1(props, key, value, function () {
    if (!isRoot && !isUpdatingChildComponent) {
      warn(
        "Avoid mutating a prop directly since the value will be " +
          "overwritten whenever the parent component re-renders. " +
          "Instead, use a data or computed property based on the prop's " +
          'value. Prop being mutated: "' +
          key +
          '"',
        vm
      );
    }
  });
}

需要特别注意的是,当你从子组件修改的prop属于基础类型时会触发提示。

这种情况下,你是无法修改父组件的数据源的,因为基础类型赋值时是值拷贝。

你直接将另一个非基础类型(Object, array)赋值到此key时也会触发提示(但实际上不会影响父组件的数据源),当你修改object的属性时不会出发提示,并且会修改父组件数据源的数据

所有的prop都使得其父子prop之间形成了一个单向下行绑定:父级prop的更新会向下流动到子组件中,但是反过来不行

这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。

同时,不仅仅是判断是否在updatingChildren中修改,当其内部还会传入第四个参数,如果不是root根组件,并且不是更新子组件,那么说明更新的是prop,所以会警告。

vue相关源码

// src/core/instance/state.js 源码路径
function initProps(vm: Component, propsOptions: Object) {
  
  const propsData = vm.$options.propsData || {};
  const props = (vm._props = {});
  // cache prop keys so that future props updates can iterate using Array
  // instead of dynamic object key enumeration.
  const keys = (vm.$options._propKeys = []);
  const isRoot = !vm.$parent;
  // root instance props should be converted
  if (!isRoot) {
    toggleObserving(false);
  }
  for (const key in propsOptions) {
    keys.push(key);
    const value = validateProp(key, propsOptions, propsData, vm);
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== "production") {
      const hyphenatedKey = hyphenate(key);
      if (
        isReservedAttribute(hyphenatedKey) ||
        config.isReservedAttr(hyphenatedKey)
      ) {
        warn(
          `"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
          vm
        );
      }
      defineReactive(props, key, value, () => {
        if (!isRoot && !isUpdatingChildComponent) {
          warn(
            `Avoid mutating a prop directly since the value will be ` +
              `overwritten whenever the parent component re-renders. ` +
              `Instead, use a data or computed property based on the prop's ` +
              `value. Prop being mutated: "${key}"`,
            vm
          );
        }
      });
    } else {
      defineReactive(props, key, value);
    }
    // static props are already proxied on the component's prototype
    // during Vue.extend(). We only need to proxy props defined at
    // instantiation here.
    if (!(key in vm)) {
      proxy(vm, `_props`, key);
    }
  }
  toggleObserving(true);
}

// src/core/observer/index.js
/**
 * Define a reactive property on an Object.
 */
export function defineReactive(
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  const dep = new Dep();

  const property = Object.getOwnPropertyDescriptor(obj, key);
  if (property && property.configurable === false) {
    return;
  }

  // cater for pre-defined getter/setters
  const getter = property && property.get;
  const setter = property && property.set;
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key];
  }

  let childOb = !shallow && observe(val);
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      const value = getter ? getter.call(obj) : val;
      if (Dep.target) {
        dep.depend();
        if (childOb) {
          childOb.dep.depend();
          if (Array.isArray(value)) {
            dependArray(value);
          }
        }
      }
      return value;
    },
    set: function reactiveSetter(newVal) {
      const value = getter ? getter.call(obj) : val;
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return;
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== "production" && customSetter) {
        customSetter();
      }
      // #7981: for accessor properties without setter
      if (getter && !setter) return;
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      childOb = !shallow && observe(newVal);
      dep.notify();
    },
  });
}

双向绑定和 vuex 是否冲突

会发生冲突;

官网案例

无需使用v-model

<input v-model="obj.message">
<!-- 错误使用方法 -->

假设这里的 obj 是在计算属性中返回的一个属于 Vuex store 的对象,在用户输入时,v-model 会试图直接修改 obj.message。在严格模式中,由于这个修改不是在 mutation 函数中执行的, 这里会抛出一个错误。

用“Vuex 的思维”去解决这个问题的方法是:给 <input> 中绑定 value,然后侦听 input 或者 change 事件,在事件回调中调用一个方法:

<input :value="message" @input="updateMessage">
// ...
computed: {
  ...mapState({
    message: state => state.obj.message
  })
},
methods: {
  updateMessage (e) {
    this.$store.commit('updateMessage', e.target.value)
  }
}

下面是 mutation 函数:

// ...
mutations: {
  updateMessage (state, message) {
    state.obj.message = message
  }
}

双向绑定的计算属性

必须承认,上面的做法比简单地使用“v-model + 局部状态”要啰嗦得多,并且也损失了一些 v-model 中很有用的特性。

另一个方法是使用带有 setter 的双向绑定计算属性:

<input v-model="message">

// ...
computed: {
  message: {
    get () {
      return this.$store.state.obj.message
    },
    set (value) {
      this.$store.commit('updateMessage', value)
    }
  }
}

vuev-for 时给每项元素绑定事件需要用事件代理吗?

  1. 数据少时可以不用,数据多时,一定要用事件代理(事件委托)。
  2. Vue框架没有为该指令做事件代理,如果需要,得我们自己做(vue本身不做事件代理)。
  3. 普通html元素和在组件上挂了.native修饰符的事件。最终EventTarget.addEventListener()挂载事件。
  4. 组件上的,vue组件实例上的自定义事件(不包括.native)会调用原型上的$on,$emit(包括一些其他api $off,$once等等)。

react代理到了document

涉及到安卓机小程序,建议直接使用代理

<body>
  <div id="app">
    <my-component></my-component>
  </div>
 
  <script src="./vue.js"></script>
  <script>
    let component = {
      template: `
        <ul @click="handleClick">
          <li v-for="(item, index) in data" :data-index="index">
            {{ item.text }}
          </li>
        </ul>
      `,
      data() {
        return {
          data: [
            {
              id: 0,
              text: '0',
            },
            {
              id: 1,
              text: '1',
            },
            {
              id: 2,
              text: '2',
            }
          ]
        }
      },
      // 通过在li元素中额外加一个data-index就可以实现委托了
      methods: {
        handleClick(e) {
          // 需要过滤掉ul,不然会出问题
          if (e.target.nodeName.toLowerCase() === 'li') {
            const index = parseInt(e.target.dataset.index)
            // 获得引索后,只需要修改data数据就能改变UI了
            this.doSomething(index)
          }
        },
        doSomething(index) {
          // do what you want
          alert(index)
        }
      }
    }

    new Vue({
      el: '#app',
      components: {
        'my-component': component
      }
    })
  </script>
</body>
# Vue  面试题 

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×