{"action":"created","issue":{"url":"https://api.github.com/repos/jirengu-inc/jrg-project-5/issues/5","repository_url":"https://api.github.com/repos/jirengu-inc/jrg-project-5","labels_url":"https://api.github.com/repos/jirengu-inc/jrg-project-5/issues/5/labels{/name}","comments_url":"https://api.github.com/repos/jirengu-inc/jrg-project-5/issues/5/comments","events_url":"https://api.github.com/repos/jirengu-inc/jrg-project-5/issues/5/events","html_url":"https://github.com/jirengu-inc/jrg-project-5/issues/5","id":202727435,"number":5,"title":"No Backend 2","user":{"login":"FrankFang","id":839559,"avatar_url":"https://avatars.githubusercontent.com/u/839559?v=3","gravatar_id":"","url":"https://api.github.com/users/FrankFang","html_url":"https://github.com/FrankFang","followers_url":"https://api.github.com/users/FrankFang/followers","following_url":"https://api.github.com/users/FrankFang/following{/other_user}","gists_url":"https://api.github.com/users/FrankFang/gists{/gist_id}","starred_url":"https://api.github.com/users/FrankFang/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/FrankFang/subscriptions","organizations_url":"https://api.github.com/users/FrankFang/orgs","repos_url":"https://api.github.com/users/FrankFang/repos","events_url":"https://api.github.com/users/FrankFang/events{/privacy}","received_events_url":"https://api.github.com/users/FrankFang/received_events","type":"User","site_admin":false},"labels":[{"id":514959069,"url":"https://api.github.com/repos/jirengu-inc/jrg-project-5/labels/%E6%95%99%E7%A8%8B","name":"教程","color":"128A0C","default":false}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":19,"created_at":"2017-01-24T05:20:24Z","updated_at":"2017-02-06T09:56:42Z","closed_at":null,"body":"```bash\r\ncp -r step-4 step-5\r\ncd step-5\r\n# 如果是 Windows 系统，再执行下面两行\r\nrm -rf node_modules\r\nnpm i\r\n# 结束 Windows 系统的命令\r\nwebpack --watch\r\n# 然后新开一个窗口\r\n```\r\n\r\n# 数据关联\r\n\r\n之前一个任务，虽然我们应该支持注册和登录了，但是用户的数据依然存在于 localStorage，这个任务我们将把数据存到用户名下，也就是让数据与用户关联（associate）起来。\r\n\r\n这次任务与之前的任务有一个不同点，那就是我们尝试一些错误的方法。（尝试之后才发现是错的）\r\n为什么要尝试错误的方法呢。\r\n因为你工作中很难一下子就找到正确的方法呀。\r\n\r\n\r\n# 保存 todo\r\n\r\n之前的任务里，我们是在窗口关闭的时候将数据保存到 localStorage，这次我们想当然地，计划在窗口关闭的时候将数据保存到 LeanCloud。\r\n\r\n## 第一次尝试\r\n\r\n我们要用的 API 基本都在《[数据存储开发指南 · JavaScript](https://leancloud.cn/docs/leanstorage_guide-js.html)》里。\r\n\r\n> commit: [页面关闭或刷新时保存数据](https://github.com/jirengu-inc/jrg-project-5/commit/e968aca2fc4708bcd5ac73ff0fa13da26c060a47)（看代码时请忽略 bundle.js）\r\n\r\n请按照上述代码改写你的代码。\r\n\r\n我们刷新页面之后，发现了一个严重的问题。\r\n\r\n我们无法调试这段代码！\r\n\r\n为什么呢？因为普通的请求如果发出去，我们是可以看见 Network 里面有一个请求的。\r\n但是这次的代码是写在 `window.onbeforeunload` 里的，所以这个请求一发出，页面就刷新了，Network 也清空了。\r\n\r\n怎么办？\r\n\r\n这就是真实项目中遇到的问题。\r\n\r\n这个时候有两个办法，\r\n\r\n1. 使用 debugger \r\n\r\n    我们在 avTodos.save 这句话结束后，写上一句 debugger:\r\n    ```\r\n      avTodos.save().then(function (todo) {\r\n        // 成功保存之后，执行其他逻辑.\r\n        console.log('保存成功');\r\n      }, function (error) {\r\n        // 异常处理\r\n        console.error('保存失败');\r\n      });\r\n      debugger // 👈\r\n\r\n    ```\r\n    然后刷新页面，然后再刷新页面。就发现断点设置成功了。\r\n    现在去 Network 看看有没有保存 todos 的请求。\r\n    居然还是没有……看了这个时候请求还没有发出。\r\n    那么我们到底要怎么看到这个请求呢？\r\n2. 使用 `preserve log`\r\n    ![](http://p1.bpimg.com/567571/e922d2638986e1df.png)\r\n    勾选这个玩意，就能让页面刷新时不清空 Network。\r\n    我们删掉 debugger，刷新页面，然后再刷新页面，看看请求有没有发出。\r\n    为什么要刷新两次？第一次是让新代码载入页面，第二次是为了触发 beforeunload 事件。\r\n    ![](http://p1.bpimg.com/567571/772b3c2554388a70.png)\r\n    从结果可以看到，AllTodos 保存请求失败了，被 `canceled` 了。\r\n    浏览器为什么会把我的请求取消掉呢？\r\n    老司机一想就猜到了：\r\n    > 如果一个页面就要死了（刷新就表示不要当前页面了，当前页面可以死了），那么这个页面发出的请求其实就没有任何意义了。既然没有意义，浏览器为什么浪费时间去发这个页面里的请求呢？所以浏览器直接取消了这个请求。\r\n\r\n    简单来说，那就是：beforeunload 事件里面的所有请求都发不出去，会被取消！\r\n    我还从来没有在哪一本书里看到过这个知识点。所以说「实践」是非常重要的。\r\n\r\n至此，我们的第一次保存数据的尝试就宣告失败了，因为我们在 beforeunload 事件里不能存将数据存到 LeanCloud。\r\n\r\n## 第二次尝试\r\n\r\n一般新人第一次尝试失败就会沮丧了。但是真正的程序员，怎么可能被一次失败打倒。接下来我们尝试第二次。\r\n\r\n注意，一旦你推导出 beforeunload 行不通，就不要再死磕它了。你必须推翻以前的思维，「重新思考」。\r\n\r\n重新思考我们的目的是什么。\r\n\r\n我们是不是希望把用户的数据保存在 LeanCloud？那么我们可不可以在用户对数据进行操作的时候马上就把数据存到 LeanCloud，也就是**在每次用户新增、删除 todo 的时候，就发送一个请求**。\r\n\r\n> commit: [addTodo 时 saveTodos，removeTodo 时也 saveTodos](https://github.com/jirengu-inc/jrg-project-5/commit/0562fb9b90ef87b5db58e4ebcc43fc5966428ce2)\r\n\r\n刷新页面，\r\n\r\n1. 新增一个 todo，看到保存的请求\r\n2. 删除一个 todo，也看到保存的请求\r\n\r\n就说明你成功了。\r\n\r\n# 读取 todo\r\n\r\n存完数据，就要读数据了。\r\n\r\n这个时候我们想想这个功能要怎么做。怎么读数据。\r\n\r\n每个 todo 都有一个 id，我可以通过 id 查询到对应的 todo，但是我们怎么知道当前用户有哪些 todo 呢？\r\n\r\n好像……没法……知道……\r\n\r\n我们目前没有办法知道当前用户有哪些 todo ……\r\n\r\n恭喜你，你要返工了。我们保存 todo 的逻辑有问题：没有将用户和 todo 关联起来。\r\n\r\n这种现象在实际工作中时有发生（尤其是新手），由于你在开始做项目的时候，没有考虑清楚，最后你发现你的代码根本无法完成需求！\r\n\r\n这说明你的思考不够全面。\r\n\r\n我怎么才能思考全面呢？\r\n\r\n多踩坑。坑踩多了，经验就多了。今天的踩坑，是为了避免明天的踩坑~\r\n\r\n# 重新设计保存逻辑\r\n\r\n1. todo 存在用户名下\r\n2. 只有 todo 所属的用户能读写这些 todo\r\n\r\n由于你现在对「ACL（Access Control List）」没有概念，所以你只能听我告诉你，我们可以使用 LeanCloud 提供的 ACL 功能来实现上面两个功能。\r\n\r\n翻看《数据存储开发指南 · JavaScript》，找到「角色」这一章节，你会看到一个链接：[JavaScript 权限管理使用指南](https://leancloud.cn/docs/acl_guide-js.html)\r\n\r\n参考文档，写出下面代码：\r\n\r\n> commit: [添加访问控制](https://github.com/jirengu-inc/jrg-project-5/commit/d74b3b3115e1261b090f4810d73dfef93cc966ce)\r\n\r\n# 重新读取 todo\r\n\r\n这个时候就要看你读文档的能力了。\r\n\r\n我们很容易在文档里找到一个 [根据 id 获取数据的 API](https://leancloud.cn/docs/leanstorage_guide-js.html#创建查询实例)。\r\n\r\n但是很遗憾这不是我们想要的，因为我们根本没有 id 可用。\r\n\r\n继续翻文档。\r\n\r\n找啊找啊找，你发现有一个「例子」是不需要 id 也能获取数据的，那就是「[批量操作](https://leancloud.cn/docs/leanstorage_guide-js.html#批量操作)」的例子\r\n\r\n```\r\n  var query = new AV.Query('Todo');\r\n  query.find().then(function (todos) {\r\n    todos.map(function(todo) {\r\n      todo['status'] = 1;\r\n    });\r\n    return AV.Object.saveAll(todos);\r\n  }).then(function(todos) {\r\n    // 更新成功\r\n  }, function (error) {\r\n    // 异常处理\r\n  });\r\n```\r\n\r\n现在还不确定行不行，那就「先试试」。\r\n\r\n> commit: [获取 User 的 AllTodos](https://github.com/jirengu-inc/jrg-project-5/commit/ab6dad66075d7f0c8689f4f1d06cbf133c2e7c24)\r\n\r\n刷新页面，看到控制台拿到数据了！\r\n\r\n![](http://p1.bqimg.com/567571/e3614db662c93627.png)\r\n\r\n但是为什么是个数组？\r\n\r\n一个用户的 AllTodos 应该只有一个，而不是多个。\r\n\r\n原因是，我们存了多个 AllTodos。\r\n\r\n我们在用户添加一个 todo 的时候，存了一个 AllTodos；\r\n在用户添加第二个 todo 的时候，又存了一个「新的」AllTodos；\r\n在用户删除一个 todo 的时候，我们又又存了一个「新的」AllTodos……\r\n\r\n也就是说，我们的保存逻辑还是有问题。\r\n\r\n正确的保存逻辑是：\r\n\r\n1. 如果发现当前用户没有存过 AllTodos，那么就存一个「新的」AllTodos\r\n2. 如果发现当前用户存过 AllTodos，那么就应该更新「之前的」AllTodos\r\n\r\n我们怎么知道用户有没有存过 AllTodos 呢？根据 id，有 id 就表示这是数据库中存在的记录，没有 id 就表示是还没保存的记录。\r\n\r\n# 又又又写一次存储逻辑\r\n\r\n首先，我们去 LeanCloud 的控制面板把 AllTodos 表清空一下：\r\n\r\n![](http://p1.bpimg.com/567571/175fed5842ebe759.png)\r\n\r\n然后重写存储逻辑：\r\n\r\n> commit: [根据 id 选择 save(create) 或者 update](https://github.com/jirengu-inc/jrg-project-5/commit/985bcb6bebbedd508ab544728431c7b2fe22ef37)\r\n\r\n然后我们把一些 alert 去掉，免得惊吓到用户\r\n\r\n> commit: [去除多余的 alert，使用对用户无打扰的 console.log](https://github.com/jirengu-inc/jrg-project-5/commit/a2b535155def4d3cdefaf3faf1bd6a69d1c46f76)\r\n\r\n# 测试！\r\n\r\n接下来我们要测试「不同的用户是否得到不同的 todos」。\r\n\r\n1. 创建用户 123123\r\n2. 添加几个 todo，内容分别是 1、2、3\r\n3. 刷新页面，我应该看到 1、2、3 三条 todo\r\n4. 登出\r\n5. 创建用户 456456\r\n6. 登录后，我不应该看到 1、2、3 三条 todo\r\n\r\n这样测试，如果成功，就能说明 456456 看不见 123123 创建的 todo 了。\r\n\r\n# BUG！\r\n\r\n测试的时候，发现一个 bug：用户退出 123123 再重新登录 123123，居然看不到自己创建的 todo。\r\n\r\n要解决这个 bug，只需要在用户登录后去读取一下 AllTodos 就行了：\r\n\r\n> commit: [登录后读取 todos](https://github.com/jirengu-inc/jrg-project-5/commit/90753659ae6687a0f9217e72480e5e7f5fe403eb)\r\n\r\n如果你发现不了这个 bug，就只能等你的用户发现这个 bug 了。\r\n\r\n那个时候，场面就不太好看了……\r\n\r\n# 致饥人谷学员\r\n\r\n我们的 todo 应用就做到这里了，通过这个应用，我们已经大概知道了一个简单的动态页面改怎么做了，接下来我们就开始做在线简历应用了。\r\n\r\n请完成上面的所有功能，并且自行测试10遍以上。\r\n\r\n自行测试10遍以上！\r\n自行测试10遍以上！\r\n\r\n然后把预览链接放到下面。\r\n\r\n# 注意 app id 和 app key\r\n\r\n我今天发现有些同学并没有在 LeanCloud 上建立应用，而是直接用了我的 APP ID 和 APP KEY\r\n\r\n```\r\nlet APP_ID = '8axnRtGoxCJhEzsvNPEAHnol-gzGzoHsz';\r\nlet APP_KEY = '0YH4XkYflb4CUPfA743TGj8G';\r\n```\r\n\r\n我请你们，换成自己的 APP ID 和 APP KEY！\r\n\r\n否则哪一天我心情不好把我的应用删了，你的应用就没法读写数据了！\r\n\r\n另外，春节快乐！"},"comment":{"url":"https://api.github.com/repos/jirengu-inc/jrg-project-5/issues/comments/277635285","html_url":"https://github.com/jirengu-inc/jrg-project-5/issues/5#issuecomment-277635285","issue_url":"https://api.github.com/repos/jirengu-inc/jrg-project-5/issues/5","id":277635285,"user":{"login":"Zegendary","id":17905889,"avatar_url":"https://avatars.githubusercontent.com/u/17905889?v=3","gravatar_id":"","url":"https://api.github.com/users/Zegendary","html_url":"https://github.com/Zegendary","followers_url":"https://api.github.com/users/Zegendary/followers","following_url":"https://api.github.com/users/Zegendary/following{/other_user}","gists_url":"https://api.github.com/users/Zegendary/gists{/gist_id}","starred_url":"https://api.github.com/users/Zegendary/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Zegendary/subscriptions","organizations_url":"https://api.github.com/users/Zegendary/orgs","repos_url":"https://api.github.com/users/Zegendary/repos","events_url":"https://api.github.com/users/Zegendary/events{/privacy}","received_events_url":"https://api.github.com/users/Zegendary/received_events","type":"User","site_admin":false},"created_at":"2017-02-06T09:56:42Z","updated_at":"2017-02-06T09:56:42Z","body":"[page](https://zegendary.github.io/jrg-pro/vue-step-4/pages.html)\r\n[code](https://github.com/Zegendary/jrg-pro/tree/master/vue-step-4)\r\nZXW"}}
{"actor":{"display_login":"Zegendary"}}