Giter Club home page Giter Club logo

code-for-vue-3-book's Issues

【387页】语病

第二行,“把回调函数存储到 context.nodeTransforms” 改为 “把回调函数存储到 transforms”

【21页】代码缩进不一致

在本书第 21 页的第二段代码(用 Composition API 来编写代码)中,第 4 行是否应当与第 3 行的缩进相一致?

image

【109-110页】示例代码,浅响应没有调用track收集响应依赖

浅响应不会执行 track 函数收集依赖,因此 target 本身的属性也不是响应式的
track 函数执行时机应在 if (isShallow) { ... } 判断语句之前

书上:

// 封装 createReactive 函数,接收一个参数 isShallow,代表是否为浅响应,默认为 false,即非浅响应
function createReactive(obj, isShallow = false) {
  return new Proxy(obj, {
    // 拦截读取操作
    get(target, key, receiver) {
      // 代理对象可以通过 raw 属性访问原始数据
      if (key === 'raw') {
        return target
      }

      const res = Reflect.get(target, key, receiver)
      // 如果是浅响应,则直接返回原始值
      if (isShallow) {
        return res
      }

      track(target, key)

      if (typeof res === 'object' && res !== null) {
        return reactive(res)
      }

      return res
    }
    // 省略其他拦截函数
  })
}

调整了下 track(target, key) 位置
修改后:

// 封装 createReactive 函数,接收一个参数 isShallow,代表是否为浅响应,默认为 false,即非浅响应
function createReactive(obj, isShallow = false) {
  return new Proxy(obj, {
    // 拦截读取操作
    get(target, key, receiver) {
      // 代理对象可以通过 raw 属性访问原始数据
      if (key === 'raw') {
        return target
      }
      track(target, key)

      const res = Reflect.get(target, key, receiver)
      // 如果是浅响应,则直接返回原始值
      if (isShallow) {
        return res
      }

      if (typeof res === 'object' && res !== null) {
        return reactive(res)
      }

      return res
    }
    // 省略其他拦截函数
  })
}

【198页】事件的处理逻辑有遗漏

if (/^on/.test(key)) {
    const invokers = el._vei || (el._vei = {})
    let invoker = invokers[key]
    const name = key.slice(2).toLowerCase()
    if (nextValue) {
      if (!invoker) {
        invoker = el._vei[key] = (e) => {
          if (Array.isArray(invoker.value)) {
            invoker.value.forEach(fn => fn(e))
          } else {
            invoker.value(e)
          }
        }
        invoker.value = nextValue
        el.addEventListener(name, invoker)
      } else {
        invoker.value = nextValue
      }
    } else if (invoker) {
      el.removeEventListener(name, invoker)
      // 移除绑定的同时需要置空或删除 el._vei
    }
  }

如上面代码注释出所述,如果新的绑定事件不存在,且之前绑定的invoker 存在,则移除绑定。但是移除绑定后invoker 依旧存在,所以下次再添加新的绑定事件时,只会触发invoker.value = newValue,而不会执行addEventListener(name, invoker)。因此,在这边需要同时置空或删除 el._vei,如el._vei = null

如下面例子所示:

const vnode = {
  type: 'p',
  props: {
    onClick: () => {
      alert('clicked 1')
    }
  },
  children: 'text'
}
renderer.render(vnode, document.querySelector('#app'))

const newVnode = {
  type: 'p',
  props: {},
  children: 'text'
}
renderer.render(newVnode, document.querySelector('#app'));
renderer.render(vnode, document.querySelector('#app'));

点击p 标签不会触发click 事件,不符合预期。

同样的问题在事件的处理章节后续的代码示例中同样存在。

【232】代码注释

代码中的注释”新的虚拟节点“有的使用"newVnode"有的使用"newVNode"

【60页】避免无限递归循环 的处理逻辑有遗漏

书中代码解决了单个副作用函数 obj.foo++ 造成的死循环问题。

effect(() => obj.foo++)

但是没有解决或提及多个副作用函数造成的死循环问题。

effect(() => obj.foo++);
effect(() => obj.foo++);

运行示例图

image

【96页】最底部示例代码 setter 缺少 receiver 入参

line 03 缺少 receiver 入参

书中:

set(target, key, newVal) {
  // 设置属性值
  const res = Reflect.set(target, key, newVal, receiver)
  // ...
}

应修改为:

set(target, key, newVal, receiver) {
  // 设置属性值
  const res = Reflect.set(target, key, newVal, receiver)
  // ...
}

【137页】注释错误

新137 页,第一段代码,第 7 行注释,由 “删除。。。” 改为 "添加。。。"

【111页】正文第2行

我们还修改了 get 拦截函数和 deleteProperty 拦截函数;

期望

我们还修改了 set 拦截函数和 deleteProperty 拦截函数;

[Bug] 第 2 章 - 2.6 错误处理 - callWithErrorHandling(page23)

当用户不去注册错误处理程序 utils.registerErrorHandler((e) => {}),handleError 为 null,执行报错 handleError is not a function

let handleError = null
export default {
  foo(fn) {
    callWithErrorHandling(fn)
  },

  bar(fn) {
    callWithErrorHandling(fn)
  },

  registerErrorHandler(fn) {
    handleError = fn
  }
}

function callWithErrorHandling(fn) {
  try {
    fn && fn()
  } catch (e) {
    handleError(e)
  }
}
function callWithErrorHandling(fn) {
  try {
    fn && fn()
  } catch (e) {
    handleError && handleError(e)
  }
}

【74页】watch 的实现原理 建议增加读者注意

目前书中所实现的代码,只能在监听有返回的 getter 函数时,才能将 newValue,oldValue 正确传入,其他情况都是 undefined。
有返回的函数是指:

// 1. 简写箭头函数
() => obj.foo; // 收集 obj.foo 的值为 newValue, oldValue

// 2. 完整写法
() => {return obj.foo};

// 3. 监听多个 getter
// 收集 obj.bar 的值为 newValue, oldValue;如果没有返回值,则 newValue 和 oldValue 都是 undefined
() => {obj.foo; return obj.bar};

测试示例图:

image

原因分析:

代码中 newValueoldValue 都是通过 effectFn() 的执行结果赋值的。而 effectFn() 的执行结果又依赖于 副作用函数 的执行结果。在当前上下文中 getter() 的返回值决定了 newValue,oldValue 的值

image

[109页] 实例代码16行,代码顺序错误

const res = Reflect.get(target, key, receiver);
if (isShallow) {
  return res;
}

track(target, key);             // 这一行应该放在isshlallow 判断之前,否则如果是浅响应就无法收集依赖了

【86页】代码有误

image

这里有段代码里,由于没有访问 receiver,所以返回值仍然是一,需要将

const obj = { foo: 1 }
console.log(Reflect.get(obj, 'foo', { foo: 2 }))

改为:

const obj = {
  get foo() {
    return this.foo
  }
}
console.log(Reflect.get(obj, 'foo', { foo: 2 }))

【261 页】代码有误

while (...) {
  if (!oldStartVNode) {
    oldStartVNode = oldChildren[++oldStartIdx]
  } else if (!oldEndVNode) {
    oldEndVNode = newChildren[--oldEndIdx]
  }
  ...
}

第五行应该为 oldEndVNode = oldChildren[--oldEndIdx]

【221页】代码错误

221页示例代码第15行

patch(oldChildren[i], newChildren[i])

应改为

patch(oldChildren[i], newChildren[i], container)

否则oldChildren 中如果有 Fragment 会出错。

【67页-69页】计算属性 computed 与 lazy 思维有点跳

【67页】无缓存的 computed 实现,当 obj.fooobj.bar 值变更时都会执行副作用函数,这个执行没有任何意义,因为执行后的结果并不会被收集。

  • 处理方案:添加调度器,在调度器中不做任何处理。
const effectFn = effect(getter, {
  lazy: true,
  scheduler() {}
})

【69页】增加调度器的配置,这里调度器是 ”空参“,看书时比较困扰为什么要用 “空参”。

  • 建议对空参进行说明。或者就是先解决上面提到的问题自然就引出了空参调度器的配置。

【259页】图 10-19 错误

该图的 newStartIdx 应该指向 p-4。
因为前文说的是 “经过上述两个步骤后”,此时队头指针已经移动了。所以应该修改

【213页】错别字

213页 倒数第二段最后一行 “我们就可以用它来描述Itmes.vue组件的模板了”
Itmes => Items

【57 页】`data.foo` 应为 `obj.foo`

在这种情况下,我们希望当修改 data.foo 时会触发 effectFn1 执行

应该是,“在这种情况下,我们希望当修改 obj.foo 时会触发 effectFn1 执行”

【44页】语句不通顺

下面划线的部分,是不是改成“我们可以按照如下所示的方式使用”更合适一些。

image

【77页】立即执行的 watch 与回调执行时机 代码调整建议

建议将 flush is post 时的原始代码:

    scheduler: () => {
      // 在调度函数中判断 flush 是否为 'post',如果是,将期放到微任务队列中执行
      if (options.flush === "post") {

        const p = Promise.resolve();
        p.then(job);

      } else {
        job();
      }
    },

变更为:

    scheduler: () => {
      // 在调度函数中判断 flush 是否为 'post',如果是,将期放到微任务队列中执行
      if (options.flush === "post") {

        jobQueue.add(job);
        flushJob();

      } else {
        job();
      }
    },

原始代码的运行结果,容易使读者产生困惑,并且新代码因为采用微任务队列,所以只会执行一次。

运行对比图:

image

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.