image.png

一、目标

  • vue/vuex实战
  • axios二次配置
  • intersectionObserver终极应用

    1. 实现图片懒加载、实现数据的下拉刷新
  • 骨架屏Skeleton技术

  • 使用Object.freeze冻结数据,提高性能,不用去拦截get、set方法
  • 使用koa,node服务提供web API

二、实现

1、vue.config.js 配置

  1. module.exports = {
  2. lintOnSave:false,
  3. devServer:{
  4. // 跨域请求:PROXY代理
  5. proxy:{
  6. '/':{
  7. target:"http://127.0.0.1:8000",
  8. changeOrigin:true
  9. }
  10. }
  11. }
  12. }

2、 动态计算rem

index.html

  1. <script>
  2. (function() {
  3. function computed() {
  4. let HTML = document.documentElement,
  5. winW = HTML.clientWidth,
  6. desW = 750;
  7. if (winW >= desW) {
  8. HTML.style.fontSize = '100px';
  9. return;
  10. }
  11. HTML.style.fontSize = 100 * (winW / desW) + 'px';
  12. }
  13. computed();
  14. window.addEventListener('resize', computed);
  15. })();
  16. </script>

3、main.js

  1. import Vue from 'vue';
  2. import App from './App.vue';
  3. import api from './api/index';
  4. import router from './router/index';
  5. Vue.config.productionTip = false;
  6. Vue.prototype.$api = api; // this.$api.xxx()
  7. new Vue({
  8. router,
  9. render: (h) => h(App),
  10. }).$mount('#app');

4、 App.vue

  1. <template>
  2. <div id="app">
  3. <heade></heade>
  4. <router-view></router-view>
  5. </div>
  6. </template>
  7. <script>
  8. import heade from './components/Header';
  9. export default {
  10. name: 'App',
  11. components: {
  12. heade,
  13. },
  14. created() {},
  15. };
  16. </script>
  17. <style>
  18. * {
  19. padding: 0;
  20. margin: 0;
  21. font-size: 0.24rem;
  22. }
  23. </style>

5、 router配置

src/router.index.js

  1. import Vue from 'vue'
  2. import VueRouter from 'vue-router';
  3. /*导入需要渲染的组件*/
  4. import Home from "../view/Home.vue"
  5. import Detail from "./view/Detail.vue"
  6. Vue.use(VueRouter)
  7. const router = new VueRouter({
  8. mode:'hash',
  9. routes:[{
  10. path:'/',
  11. name:'home',
  12. component:Home
  13. },{
  14. path:'/',
  15. name:'detail',
  16. component:Detail
  17. },{
  18. path:'*',
  19. redirect:'/'
  20. }]
  21. })
  22. export default router;

路由面试相关问题
1、路由的模块开发
2、路由懒加载 原理 通过webpack 分块加载
3、路由传参
4、路由守卫 => 权限校验的N种方案
5、HASH/BEOWSER路由原理和区别

6、封装axios

fetch脱离了xml
http.js
封装axios
src/api/http.js

  1. import axios from 'axios';
  2. import qs from 'qs';
  3. axios.defaults.baseURL = "";
  4. axios.defaults.withCredentials = true; // 允许跨域携带资源凭证
  5. axios.defaults.headers['Content-Type'] = 'application/x-www-form-urlencoded'
  6. axios.defaults.transformRequest=data=>qs.stringify(data) // post => xxx=xxx
  7. axios.defaults.timeout = 0; // 超时时间
  8. // 请求拦截器 客户端向服务端发送之前
  9. axios.interceptors.request.use(config=>{
  10. // config 包含所有请求的配置信息
  11. // config.headers['X-token'] = 'xxx'
  12. // 把token信息发给服务器
  13. return config
  14. })
  15. // 响应拦截器,从服务器获取的结果进行统一处理
  16. axios.interceptors.response.use(response=>{
  17. // 处理成功,默认认为状态码以2开头
  18. return response.data
  19. },reason=>{
  20. // 失败 1、获取了结果但是状态码不是2开头 2、压根没有从服务器获取数据
  21. if(reason.response){
  22. // 获取到数据了,根据不同状态码做不同提示
  23. }else{
  24. // 没有获取数据
  25. if(!window.navigator.onLine){
  26. // 断网
  27. }
  28. }
  29. return Promise.reject(reason)
  30. })
  31. // axios.defaults.validate...
  32. export default axios;

src/api/index.js

  1. import axios from './http'
  2. function QUERY_LIST(){
  3. return axois.get('/list')
  4. }
  5. export default {
  6. QUERY_LIST
  7. }

7、 首页

view/Home.vue

  1. <template>
  2. <div class="homeBox">
  3. <Skeleton v-if="list.length === 0"></Skeleton>
  4. <div class="content" v-else>
  5. <Item v-for="(item, index) in list" :key="index" :data="item" />
  6. </div>
  7. <div ref="loadMore">
  8. {{ list.length > 0 ? 'loadMore加载更多。。。' : '' }}
  9. </div>
  10. </div>
  11. </template>
  12. <script>
  13. import Skeleton from '../components/Skeleton';
  14. import Item from '../components/Item';
  15. import Util from '../util/index';
  16. export default {
  17. name: 'Home',
  18. components: {
  19. Skeleton,
  20. Item,
  21. },
  22. data() {
  23. return {
  24. list: [],
  25. // has_more: false,
  26. };
  27. },
  28. created() {
  29. this.fetchList();
  30. },
  31. mounted() {
  32. let ob = new IntersectionObserver(
  33. (changes) => {
  34. changes.forEach((item) => {
  35. if (item.isIntersecting && this.list.length > 0) {
  36. this.fetchList();
  37. }
  38. });
  39. },
  40. {
  41. // 控制元素出现的时机出触发函数 0 是视图中只要有出现在视图中 0.5 一半时 1是完全在视图中触发
  42. threshold: [0],
  43. }
  44. );
  45. ob.observe(this.$refs.loadMore);
  46. },
  47. methods: {
  48. async fetchList() {
  49. await Util.sleep(2000); // 等待看效果
  50. let result = await this.$api.QUERY_LIST();
  51. this.has_more = result.has_more;
  52. this.list = Object.freeze([...this.list, ...result.data]);
  53. },
  54. },
  55. };
  56. </script>
  57. <style scoped>
  58. .homeBox {
  59. text-align: center;
  60. }
  61. .content {
  62. padding: 0 0.3rem;
  63. }
  64. </style>

8、组件划分

  • 组件划分
  • 组件库的二次封装
  • 自己单独封装组件、构建敏捷化平台、如何封装打造开源级

component/Header.vue

  1. <template>
  2. <div class="abs_m">
  3. 今日头条
  4. </div>
  5. </template>
  6. <script>
  7. export default {
  8. name: 'heade',
  9. };
  10. </script>
  11. <style scoped>
  12. .abs_m {
  13. line-height: 88px;
  14. width: 100%;
  15. text-align: center;
  16. font-size: 20px;
  17. color: #fff;
  18. font-weight: bold;
  19. line-height: 44px;
  20. background: #d43d3d;
  21. }
  22. </style>

component/Item.vue

  1. <template>
  2. <div>
  3. <router-link
  4. v-if="data.image_url"
  5. :to="{ path: `/detail/${data.item_id}` }"
  6. :class="['item', data.image_url ? 'itemA' : 'itemB']"
  7. >
  8. <div class="text">
  9. <p>{{ data.title }}</p>
  10. <span>{{ data.media_name }}</span>
  11. </div>
  12. <div class="img_box" ref="lazyImg">
  13. <img src :data-img="data.image_url" alt />
  14. </div>
  15. </router-link>
  16. <a href v-if="Array.isArray(data.image_list) && data.image_list.length > 0">
  17. <div class="text">
  18. <p>{{ data.title }}</p>
  19. <span>{{ data.media_name }}</span>
  20. </div>
  21. <div class="imageList">
  22. <div
  23. class="img_box"
  24. ref="lazyImg"
  25. v-for="(item, index) in data.image_list"
  26. :key="index"
  27. >
  28. <img :data-img="item.url" src="" alt="" />
  29. </div>
  30. </div>
  31. <span class="mark">央视新闻</span>
  32. </a>
  33. </div>
  34. </template>
  35. <script>
  36. export default {
  37. props: {
  38. data: {
  39. type: Object,
  40. required: true,
  41. },
  42. },
  43. mounted() {
  44. let ob = new IntersectionObserver(
  45. (changes) => {
  46. changes.forEach((item) => {
  47. if (item.isIntersecting) {
  48. let target = item.target;
  49. this.lazyImg(target);
  50. ob.unobserve(target);
  51. }
  52. });
  53. },
  54. {
  55. // 控制元素出现的时机出触发函数 0 是视图中只要有出现在视图中 0.5 一半时 1是完全在视图中触发
  56. threshold: [1],
  57. }
  58. );
  59. let lazyImg = this.$refs.lazyImg;
  60. Array.isArray(lazyImg)
  61. ? lazyImg.forEach((el) => {
  62. ob.observe(el);
  63. })
  64. : lazyImg && ob.observe(lazyImg);
  65. },
  66. methods: {
  67. lazyImg(imgBox) {
  68. let img = imgBox.querySelector('img'),
  69. true_img = img.getAttribute('data-img');
  70. img.onload = () => {
  71. img.style.opacity = 1;
  72. };
  73. img.src = true_img;
  74. img.removeAttribute('data-img');
  75. },
  76. },
  77. };
  78. </script>
  79. <style lang="less" scoped>
  80. a {
  81. display: block;
  82. color: #000;
  83. }
  84. img {
  85. opacity: 0;
  86. transition: opacity 0.3s;
  87. }
  88. .img_box {
  89. display: block;
  90. background: #eee;
  91. }
  92. .imageList {
  93. width: 100%;
  94. display: flex;
  95. overflow: hidden;
  96. justify-content: center;
  97. .img_box {
  98. width: 33%;
  99. }
  100. }
  101. .itemA {
  102. display: flex;
  103. justify-content: space-between;
  104. align-items: center;
  105. .text {
  106. width: 60%;
  107. p {
  108. }
  109. }
  110. .img_box {
  111. width: 2.2rem;
  112. height: 1.47rem;
  113. img {
  114. width: 100%;
  115. height: 100%;
  116. }
  117. }
  118. }
  119. </style>

使用Object.freeze冻结数据,提高性能,不用去拦截get、set方法
通过data-img存一下url,通过出现在视图中把图片地址赋给src
keep-alive 性能优化列表, 返回列表的时候不重新渲染页面

9、server接口服务

server/app.js

  1. // 参考:https://www.cnblogs.com/chanwahfung/p/11415675.html
  2. var Koa = require('koa');
  3. var Router = require('koa-router');
  4. var bodyParser = require('koa-bodyparser');
  5. var fs = require('fs');
  6. const cors = require('koa2-cors');
  7. var app = new Koa();
  8. var router = new Router();
  9. // 设置头部信息
  10. app.use(
  11. cors({
  12. origin: function(ctx) {
  13. return '*'; //cors
  14. },
  15. exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'],
  16. maxAge: 5,
  17. credentials: true,
  18. allowMethods: ['GET', 'POST'],
  19. allowHeaders: ['Content-Type', 'Authorization', 'Accept'],
  20. })
  21. );
  22. // 1. fs文件模块读取routes文件夹目录内容(获得的是一个文件名的数组)
  23. // 2. 数组遍历,引入接口文件,将文件名作为路由名,注册使用路由
  24. // 3. 使用: /list/getList
  25. let urls = fs.readdirSync(__dirname + '/routes');
  26. urls.forEach((element) => {
  27. //routes里的js接口文件
  28. let module = require(__dirname + '/routes/' + element);
  29. //routes里的文件名作为 路由名
  30. router.use('/' + element.replace('.js', ''), module.routes());
  31. });
  32. //使用路由中间件
  33. app
  34. .use(router.routes())
  35. .use(router.allowedMethods())
  36. .use(bodyParser());
  37. app.listen(8000);
  38. console.log('监听8000');

server/routes/list.js

  1. var Router = require('koa-router');
  2. var router = new Router();
  3. router.get('/getList', (ctx, next) => {
  4. let id = ctx.request.params.id;
  5. ctx.body = {
  6. id,
  7. code: 1,
  8. };
  9. });
  10. router.post('/submit', (ctx) => {
  11. ctx.body = {
  12. code: 1,
  13. postParams: ctx.request.body,
  14. };
  15. });
  16. module.exports = router;
  1. const Router = require('koa-router')
  2. const route = new Router()
  3. const jwt = require('jsonwebtoken')
  4. route.get('/getToken', async (ctx)=>{
  5. let {name,id} = ctx.query
  6. if(!name && !id){
  7. ctx.body = {
  8. msg:'不合法',
  9. code:0
  10. }
  11. return
  12. }
  13. //生成token
  14. let token = jwt.sign({name,id},'secret',{ expiresIn: '1h' })
  15. ctx.body = {
  16. token: token,
  17. code:1
  18. }
  19. })
  20. route.get('/getUser', async ctx=>{
  21. let id = ctx.query.id
  22. ctx.body = {
  23. user:ctx.payload,
  24. id,
  25. code:1
  26. }
  27. })
  28. route.get('/getAllUser', async ctx=>{
  29. let type = ctx.query.type
  30. if(type){
  31. ctx.body = {
  32. type,
  33. code:1
  34. }
  35. }else{
  36. ctx.body = {
  37. msg:'缺少参数type',
  38. code:0
  39. }
  40. }
  41. })
  42. module.exports = route