Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

开发技巧挑战 100 楼 #49

Open
yanyue404 opened this issue Jun 11, 2019 · 23 comments
Open

开发技巧挑战 100 楼 #49

yanyue404 opened this issue Jun 11, 2019 · 23 comments

Comments

@yanyue404
Copy link
Owner Author

yanyue404 commented Jun 11, 2019

001. JS 剔除对象中不需要的属性 + 惰性载入函数

剔除对象中不需要的属性

var original_params = {
  username: "Rainbow",
  age: "25",
  pwd: "123456",
  girlFriend: true,
  friend: ["heizi", "niu"]
};

提问

获取 usernamepwdobj

// 方法1: delete object[key]

let result = original_params;
delete result.age;
delete result.girlFriend;
delete result.friend;
// 方法2:使用解构删除不必要属性

const { friend, age, girlFriend, ...result2 } = original_params; //  Rest element must be last element

惰性载入函数

在某个场景下我们的函数中有判断语句,这个判断依据在整个项目运行期间一般不会变化,所以判断分支在整个项目运行期间只会运行某个特定分支,那么就可以考虑惰性载入函数

function foo() {
  if (a !== b) {
    console.log("aaa");
  } else {
    console.log("bbb");
  }
}

// 优化后
function foo() {
  if (a != b) {
    foo = function() {
      console.log("aaa");
    };
  } else {
    foo = function() {
      console.log("bbb");
    };
  }
  return foo();
}

第一次运行之后就会覆写这个方法,下一次再运行的时候就不会执行判断了。当然现在只有一个判断,如果判断很多,分支比较复杂,那么节约的资源还是可观的。

举一反三,既然在函数内部定义与原函数同名函数导出可以覆写原函数,在仅需让函数方法执行一次的场景也同样使用,将原函数覆写为空函数就可以了

参考

@yanyue404
Copy link
Owner Author

yanyue404 commented Jun 14, 2019

002. JS-根据条件给对象插入属性

对一个原始类型的值执行扩展运算符,它总会返回一个空对象。比如{ ...true }相当于{}。当然字符串除外,因为字符串拥有某些数组的特性。

那这个奇技淫巧的原理是什么呢?

其实是因为&&运算符的优先级比...高,所以{ ...name && { name } }相当于{ ...(name && { name }) },这样一转化就好理解了。

扩展运算符的优先级很低,仅高于,

// 通常我们都这么写

const profile = { age: 18, gender: "男" };

if (name) {
  profile.name = name;
}

// if 可以简化一下

if (name) profile.name = name;

// 你不能这样写,因为逻辑与运算符后面只能跟表达式,不能跟语句

name && profile.name = name;

// 现在你可以这样写

const profile = { age: 18, gender: "男", ...(name && { name }) };

参考

@yanyue404
Copy link
Owner Author

yanyue404 commented Jun 19, 2019

003. Node.js 的 Error-First Callbacks

  • 1.回调函数的第一个参数保留给一个错误 error 对象,如果有错误发生,错误将通过第一个参数 err 返回。
  • 2.回调函数的第二个参数为成功响应的数据保留,如果没有错误发生,err 将被设置为 null, 成功的数据将从第二个参数返回。
Page({
  // 获取用户可以领取的优惠券
  getMyCoupons(callback) {
    fetch._post(API.get_coupons, {}).then(res => {
      let err, result;
      if (res.response_data.error_code) {
        err = res.response_data;
      } else {
        result = res.response_data.lists;
      }
      typeof callback === "function" ? callback(err, result) : "";
    });
  }
});

@yanyue404
Copy link
Owner Author

yanyue404 commented Jun 24, 2019

004. 数组,对象映射优化 if... else...

  • switch-case
function getWeekDay() {
  let day = new Date().getDay();
  switch (day) {
    case 0:
      return "天";
    case 1:
      return "一";
    case 2:
      return "二";
    case 3:
      return "三";
    case 4:
      return "四";
    case 5:
      return "五";
    case 6:
      return "六";
  }
}
console.log(`今天是星期` + getWeekDay());
  • 数组映射
function getWeekDay() {
  let day = new Date().getDay();
  return ["天", "一", "二", "三", "四", "五", "六"][day];
}
console.log(`今天是星期` + getWeekDay());
  • 对象映射
function resolveWeekday() {
  let string = "今天是星期",
    date = new Date().getDay(),
    dateObj = {
      0: ["天", "休"],
      1: ["一", "工"],
      2: ["二", "工"],
      3: ["三", "工"],
      4: ["四", "工"],
      5: ["五", "工"],
      6: ["六", "休"]
    };
  dayType = {
    : function() {
      // some code
      console.log(string + "为休息日");
    },
    : function() {
      // some code
      console.log(string + "为工作日");
    }
  };
  return {
    string: string + dateObj[date][0],
    method: dayType[dateObj[date][1]]
  };
}
resolveWeekday().method();

参考

@yanyue404
Copy link
Owner Author

yanyue404 commented Jul 2, 2019

005. 从数组循环的 forEach 方法中删除元素

let data = [
  {
    name: "蔬菜类",
    thumb_img: "",
    price: 30,
    desc: "茄子",
    goods_id: "4",
    num: 1
  },
  {
    name: "越南进口红心火龙果 4个装",
    thumb_img: "",
    price: 0.01,
    goods_id: "7",
    num: 1
  }
];
let ids = ["4", "7"];
  • 方法一:反向删除 for while
let index = data.length - 1;
while (index >= 0) {
  if (ids.includes(data[index]["goods_id"])) {
    data.splice(index, 1);
  }
  index--;
}
for (var i = data.length - 1; i >= 0; i--) {
  if (ids.includes(data[i]["goods_id"])) {
    data.splice(i, 1);
  }
}
  • 方法二:浅拷贝数组:倒序修正 index 指向
// 使用 slice 浅拷贝 object 是真实的
data.slice().forEach((v, index, object) => {
  ids.includes(v["goods_id"]) ? data.splice(object.length - 1 - index, 1) : "";
});
  • 双重 forEach
data.slice().forEach((v, index, object) => {
  ids.forEach(m => {
    v.goods_id === m ? data.splice(object - 1 - index, 1) : "";
  });
});

参考

@yanyue404
Copy link
Owner Author

yanyue404 commented Jul 22, 2019

006. 优化表单验证逻辑判断

<script src="https://cdn.bootcss.com/vue/2.5.16/vue.min.js"></script>
<div id="app">
  <input type="text" v-model="username" placeholder="请输入你的用户名" />
  <input type="text" v-model="phone" placeholder="请输入你的手机号" />
  <input type="text" v-model="pwd" placeholder="请输入你的密码" />
  <button @click="submit">提交</button>
</div>
  • 方法一:数组遍历判断
var vm = new Vue({
  el: "#app",
  data: {
    username: "",
    phone: "",
    pwd: ""
  },
  methods: {
    submit: function() {
      const { username, phone, pwd } = this;
      let valids = [
        {
          valid: username,
          err: "请输入你的用户名"
        },
        {
          valid: phone,
          err: "请输入你的手机号码"
        },
        {
          valid: /^1[3|4|5|6|7|8][0-9]{9}$/.test(phone),
          err: "手机号码格式有误"
        },
        { valid: pwd, err: "请输入你的密码" }
      ];
      for (let val of valids) {
        if (!val.valid) {
          alert(val.err);
          return;
        }
      }
      alert("提交成功");
    }
  }
});
  • 方法二:Validator Class 公共类封装
class Validator {
  constructor(conditions) {
    setTimeout(() => {
      for (let item of conditions) {
        const res = item.fn();
        if (!res) {
          alert(item.err);
          return;
        }
      }
      this.callback();
    }, 0);
  }
  then(callback = () => {}) {
    this.callback = callback;
  }
}
var vm = new Vue({
  el: "#app",
  data: {
    username: "",
    phone: "",
    pwd: ""
  },
  methods: {
    submit: function() {
      const { username, phone, pwd } = this;
      let rules = [
        {
          fn: _ => username,
          err: "请输入你的用户名"
        },
        {
          fn: _ => phone,
          err: "请输入你的手机号码"
        },
        {
          fn: _ => /^1[3|4|5|6|7|8][0-9]{9}$/.test(phone),
          err: "手机号码格式有误"
        },
        { fn: _ => pwd, err: "请输入你的密码" }
      ];
      new Validator(rules).then(() => {
        alert("提交成功");
      });
    }
  }
});

参考

@yanyue404
Copy link
Owner Author

yanyue404 commented Jul 22, 2019

007. JS 的加减乘除运算

将浮点数 toString 后记录小数位的长度,然后将小数点抹掉,完整的代码如下:

Math.pow() 函数返回基数(base 例:10)的指数(exponent 例:2)次幂,Math.pow(10,2) = 100

/**
 * 加法
 * @param arg1
 * @param arg2
 * @returns
 **/
function accAdd(arg1, arg2) {
  var r1, r2, m;
  try {
    r1 = arg1.toString().split(".")[1].length;
  } catch (e) {
    r1 = 0;
  }
  try {
    r2 = arg2.toString().split(".")[1].length;
  } catch (e) {
    r2 = 0;
  }
  m = Math.pow(10, Math.max(r1, r2));
  return (arg1 * m + arg2 * m) / m;
}

/**
 * 减法
 * @param arg1
 * @param arg2
 * @returns
 */
function accSub(arg1, arg2) {
  var r1, r2, m, n;
  try {
    r1 = arg1.toString().split(".")[1].length;
  } catch (e) {
    r1 = 0;
  }
  try {
    r2 = arg2.toString().split(".")[1].length;
  } catch (e) {
    r2 = 0;
  }
  m = Math.pow(10, Math.max(r1, r2));
  //动态控制精度长度
  n = r1 >= r2 ? r1 : r2;
  return ((arg1 * m - arg2 * m) / m).toFixed(n);
}

function accMul(arg1, arg2) {
  var m = 0,
    s1 = arg1.toString(),
    s2 = arg2.toString();
  try {
    m += s1.split(".")[1].length;
  } catch (e) {}
  try {
    m += s2.split(".")[1].length;
  } catch (e) {}
  return (
    (Number(s1.replace(".", "")) * Number(s2.replace(".", ""))) /
    Math.pow(10, m)
  );
}

function accDiv(arg1, arg2) {
  var t1 = 0,
    t2 = 0,
    r1,
    r2;
  try {
    t1 = arg1.toString().split(".")[1].length;
  } catch (e) {}
  try {
    t2 = arg2.toString().split(".")[1].length;
  } catch (e) {}
  with (Math) {
    r1 = Number(arg1.toString().replace(".", ""));
    r2 = Number(arg2.toString().replace(".", ""));
    return (r1 / r2) * pow(10, t2 - t1);
  }
}

//给Number类型增加一个add方法,调用起来更加方便。
Number.prototype.add = function(arg) {
  return accAdd(arg, this);
};
Number.prototype.sub = function(arg) {
  return accSub(arg, this);
};
Number.prototype.Mul = function(arg) {
  return accMul(arg, this);
};
Number.prototype.Dev = function(arg) {
  return accMul(arg, this);
};

@yanyue404
Copy link
Owner Author

yanyue404 commented Aug 1, 2019

008. VSCode - ESLint, Prettier & Airbnb Setup

1. Install ESLint & Prettier extensions for VSCode

Optional - Set format on save and any global prettier options

2. Install Packages

npm i -D eslint prettier eslint-plugin-prettier eslint-config-prettier eslint-plugin-node eslint-config-node
npx install-peerdeps --dev eslint-config-airbnb

3. Create .prettierrc for any prettier rules (semicolons, quotes, etc)

4. Create .eslintrc.json file (You can generate with eslint --init if you install eslint globally)

{
  "extends": ["airbnb", "prettier", "plugin:node/recommended"],
  "plugins": ["prettier"],
  "rules": {
    "prettier/prettier": "error",
    "no-unused-vars": "warn",
    "no-console": "off",
    "func-names": "off",
    "no-process-exit": "off",
    "object-shorthand": "off",
    "class-methods-use-this": "off"
  }
}

Reference

@yanyue404 yanyue404 reopened this Aug 1, 2019
@yanyue404
Copy link
Owner Author

yanyue404 commented Sep 24, 2019

009. Cannot use arrow keys to choose options on Windows

  • 输入选项 number , Enter

Reference

@yanyue404
Copy link
Owner Author

yanyue404 commented Oct 14, 2019

010. Markdown 使用

  • 首行缩进

  两个 &emsp; 即可

  • 图片尺寸

<img width="100%" src="http://ww1.sinaimg.cn/large/df551ea5ly1g865qolvofj20dw08zacz.jpg"></img>

参考

@yanyue404
Copy link
Owner Author

yanyue404 commented Oct 15, 2019

011. Git commit log

  • Head
    • type: feat 新特性, fix 修改问题, docs 文档, style 格式, refactor 重构, test 测试用例,revert 还原, chore 其他修改, 比如构建流程, 依赖管理.
    • scope:影响范围, 比如: route, component, utils, build... 可省略
    • subject:简短的提交信息
  • Body
    • what:详细做了什么
    • why: 为什么这样做
    • how: 有什么后果
  • Footer
    • 相关链接

@yanyue404
Copy link
Owner Author

yanyue404 commented Oct 21, 2019

012. 代码片段编写

import Taro, { Component } from '@tarojs/taro'
import { View, Text } from '@tarojs/components'
import './index.scss'

export default class Index extends Component {
 config = {
   navigationBarTitleText: '首页'
 }

 componentWillMount () { }

 componentDidMount () { }

 componentWillUnmount () { }

 componentDidShow () { }

 componentDidHide () { }

 render () {
   return (
     <View className='index'>
       <Text>1</Text>
     </View>
   )
 }
}

转换过程

符号 含义
\n 换行
\t 缩进
{
    "tarcs": {
    "prefix": "tarc",
    "body": [
      "import Taro from '@tarojs/taro';",
      "import { View, Text } from '@tarojs/components';",
      "import styles from './index.scss'",
      "",
      "export default class ${1:Test} extends Taro.Component {",
      "  render() {",
      "    return (",
      "      <View className={styles.${2:main}}>",
      "        <Text> ${1:测试输入} </Text>",
      "      </View>",
      "    );",
      "  }",
      "}",
      ""
    ],
    "description": "taro页面模板带状态"
  },
}

@yanyue404
Copy link
Owner Author

013. Ve2x base64 解密

浏览器 F12, 在 console 面板里输入

# 加密
btoa('13122223333') => MTMxMjIyMjMzMzM=

# 解密
atob('Y3JhY2sybkBnbWFpbC5jb20=') => crack2n@gmail.com

参考

@yanyue404
Copy link
Owner Author

014. require 自动导入

/**
 * @desc webpack打包入口文件
 */
let moduleExports = {};

const r = require.context('./', true, /^\.\/.+\/.+\.js$/);
r.keys().forEach(key => {
    let attr = key.substring(key.lastIndexOf('/') + 1, key.lastIndexOf('.'));
    moduleExports[attr] = r(key);
});

module.exports = moduleExports;

参考

@yanyue404
Copy link
Owner Author

015. package.json 文件添加注释

不能加在"dependencies"里,但是可以加为package.json的最顶层项:

{
"//": "This is comment 1",
"//": "This is comment 2"
}

或者

{
"//": [ "Line 1 of Comments", "Line 2 of Comments" ]
}

来源

@yanyue404
Copy link
Owner Author

016. 给你的select添加上placeholder

为 select 下拉框添加类似 input 的placeholder 的功能呢?

<select name="source" id="source">
   <option value="" disabled="" selected="" hidden="">请选择来源</option>
   <option value="1">个人</option>
   <option value="2">学校</option>
   <option value="3">平台</option>
</select>

来源

@yanyue404
Copy link
Owner Author

017. 微信内置浏览器缓存清理

之前做过很多公众号的项目,项目写完后给客户看项目,客户一而再再而三的修改元素向左挪1px,向右挪2px。改好之后让客户看,客户说我特泽发克,你啥都没有修改,你竟然骗我!!!

这其实就是微信内置浏览器的缓存在作祟啦,那么如何清理微信内置浏览器的缓存呢?

你们是否知道 ios版微信 和 android版微信 的内置浏览器的内核是不一样的呢?

android版微信内置浏览器(X5内核)
在安卓版微信内打开链接 http://debugx5.qq.com

拉到调试页面的最底端,勾选上所有的缓存项目,点击清除。

点击确定之后即可完成清除微信浏览器缓存的操作。

ios版微信内置浏览器(WKWebView)
ios版微信内置浏览器内核并不是 X5内核,而是使用的ios的浏览器内核WKWebView,所以安卓手机的那种方案对ios手机用户不生效,因为那个链接压根打不开

只要微信用户退出登录,然后重新登录,ios版微信内置浏览器内核即可清除,不行的话,你们回来打我

有人说了:“IOS中 设置—通用----存储空间 就会看到“正在计算空间”计算完了会清理一点清理即可”,这种办法当然也可以,但是这种办法不光是清理微信内置浏览器的缓存,同时也清理其他的一些数据,比如朋友圈的视频图片和聊天记录等等缓存,而且容易误删某些想留下的数据,对于开发而言,我认为退出重新登录是最好的解决办法。

来源

@yanyue404
Copy link
Owner Author

018. 接口 API 参考文档

一、基本规则

1.1 接⼝规则

协议规则:

描述 说明
传输⽅式 采⽤HTTP、HTTPS
提交⽅式 采⽤POST⽅法提交
请求参数类型 Content-Type: application/json
响应参数类型 Content-Type: application/json
字符编码 统⼀采⽤ UTF-8 字符编码
签名算法 MD5().toUpperCase()

1.2 请求参数公共字段

字段名 变量名 必填 类型 描述
调⽤⽅帐号 appId String(32) 分配给调⽤⽅的帐号 id
随机字符串 randomStr String(32) 随机字符串,不⻓于 32 位
签名 sign String(32) 签名
签名请求时间戳(毫秒级) gmtRequest Long 请求时间,Unix 时间戳格式,如1514782861001,代表‘2018-01-01 13:01:01.001’,仅允许请求时间与系统时间五分钟内的请求

1.3 返回参数字段格式

字段名 变量名 必填 类型 描述
数据实体 data json 接⼝返回的数据放这⾥
异常信息 msg string 接⼝调⽤返回的消息
异常码 code int(11) 接⼝调⽤返回异常,异常码
接⼝调⽤状态 success boolean(1) 接⼝调⽤状态 true-成功,false-失败

二、接⼝列表

2.1 查询积分

应⽤场景:查询⽤户积分余额

接⼝链接:/

请求⽅式:POST

请求参数:

字段名 变量名 必填 类型 描述
⽤户id uid int ⽤户ID

返回参数:

字段名 变量名 必填 类型 描述
账户积分 score int 会员账户积分

请求参数示例:

{ 
  "uid": 10001, 
  "appId": "xh161344685917", 
  "gmtRequest": 1603702285590, 
  "randomStr": "621465f968e0d9022f", 
  "sign": "278967FACE51E3BD45D7EB028E0CE5C8" 
} 

返回参数示例:

{ 
 "data": { 
 "uid": 10001, 
 "data": 120
  }
 "code": 0,
 "success": true 
}

收到如下返回时结束请求:

{ 
 "msg": "用户不存在", 
 "code": 100, 
 "success": false 
} 

@yanyue404
Copy link
Owner Author

019.在 Markdown 文档显示 diff 效果

function addTwoNumbers (num1, num2) {
-  return 1 + 2
+  return num1 + num2
}

参考

@yanyue404
Copy link
Owner Author

020. Markdown展开折叠功能

默认展开

details 标签默认是折叠状态,如果想默认为展开的话,可以添加一个 open 属性:

TestA1
TestB1
  • Test B1
TestB2
TestC1
  • Test C1

参考

@yanyue404
Copy link
Owner Author

markdown里怎么加引用注释或脚注

一般我们只是在markdown添加链接,但怎么在markdown里加脚注呢?下面来看看

这里有一个注脚[^1],这段话的还有其他意思[^2]在里面
[^1]:这里是注脚内容
[^2]:这里是其他意思的注脚

注脚放到中间也可以,下面是具体效果

md_footnote.png

md中链接的另一种写法

我是一段文字,[baidu][1]、[qq][2]里面有链接
[1]: http://baidu.com "baidu"
[2]: http://qq.com "qq"

参考

@yanyue404
Copy link
Owner Author

什么是好的代码?

在web前端方面,什么是好的代码?好的代码应该包含以下两个特性

  • 高性能,低时延(性能优化)
    • 熟悉数据结构与算法,减少时间复杂度或空间复杂度
    • 熟悉浏览器渲染基本原理、熟悉HTTP请求与响应细节、熟悉前端框架源码、减少不必要的渲染开销,提高加载速度
  • 可读性、可维护性、可扩展性
    • 熟悉设计模式,封装变化。代码高内聚、低耦合、指责单一、高度复用。写出好维护、好迭代、好扩展的代码
    • 化繁为简,形成特定代码规范,注意命名、注释。写出人能看懂的代码,不做骚操作。尽量保持简单、易懂,在可扩展性和简单之间寻找平衡

前端只要不是写框架,性能问题会很少遇到。简单来讲,在实现功能的基础上,代码简单、易懂、好维护迭代就很好了。技术始终是为业务需求服务的。基础建设是很重要的一个环节,这样有利于快速迭代开发

参考

@yanyue404
Copy link
Owner Author

vue中为什么要使用js调用单文件组件?怎么实现js调用组件?

如果自己写一个组件。一般情况下,vue项目中在某个组件里调用另一个组件,至少需要修改三个位置

  1. 在 template 里写引入组件,加上传参等
  2. 在 components 里声明组件(如果全局引入了,可以省去这一步)
  3. data 里面写对应的传参数

代码对应如下,这种组件对于使用地方比较多时候,我们就需要想办法直接使用js来调用组件,而不是每次都要在 template 里面声明对应的组件,这样会有很多重复代码,可维护性较差。

<template>
  <el-button @click="showToast">打开toast</el-button>
  <!-- template中使用组件 -->
  <toast v-model="showToast"></toast>
</template>
<script>
export default {
  components: {
    Toast: () => import('./Toast.vue')
  }
  data() {
    return {
      showToast: false
    }
  }
}
</script>

较早之前使用js加载组件尝试

在较早之前,用js写过一个直接挂载组件到当前dom上的一个方法,分三步:

  1. 使用 Vue.extend,处理需要用js调用的vue单文件组件,返回该vue组件的一个子类
  2. new对应的组件子类,生成对应的组件实例,并使用 .$mount() 挂载组件,返回对应组件的 vm
  3. 这样可以通过 vm.$el 拿到组件dom,append到当前组件dom里,即可完成加载

具体写法如下

// 假设写好了 showInfo.vue 组件,执行clickShow函数直接显示dialog
// 组件中 dialog :visible.sync="dialogTableVisible"初始值设置为true

// demo.vue 在需要调用的vue文件中引入该组件
import ShowInfo from 'showInfo.vue'
// ...
clickShow() {
  const Component = Vue.extend(ShowInfo)

  // 挂载后返回对应组件的vm
  let showInfoVue = new Component().$mount()

  // 将组件vm的dom,append到当前页面
  this.$el.appendChild(showInfoVue.$el)
}
// ...

具体参考:使用js调用vue单文件组件

但它有一个缺点,常规调用组件时,我们会向子组件里面传入参数或事件。而这种情况不能向调用的子组件传入参数。下面来看另一种方法

使用js将单文件组件挂载到body的通用方法

我们来看看下面的create方法,其实和上面的方法流程基本一致,只是改变了创建对应vue单文件组件实例的方法。这里用render函数来替代之前的Vue.extend来创建对应组件实例,这样可以通过render函数的createElement函数向子组件内部传参数,传方法等。

// create.js
import Vue from "vue";

export default function create(Component, props) {
  // 先创建实例
  const vm = new Vue({
    render(h) {
      // h就是createElement,它返回VNode
      return h(Component, { props });
    }
  }).$mount();

  // 手动挂载
  document.body.appendChild(vm.$el);

  // 销毁方法
  const comp = vm.$children[0];
  comp.remove = function() {
    document.body.removeChild(vm.$el);
    vm.$destroy();
  };
  return comp;
}

上面的例子中,create函数参数接收一个单文件组件对象,以及在调用组件时需要传递给组件的参数,来看看具体使用例子

<template>
  <div>
    <el-button @click="showToast">打开toast</el-button>
  </div>
</template>
<script>
import Toast from "./Toast.vue";
import create from "./create";
export default {
  methods: {
    showToast() {
      console.log("show toast");
      let toast = create(Toast, {
        show: true,
        message: "我是错误信息",
        type: "error"
      });
      // 等价于 <toast :show="true" message="xx" :type="error"></toast>
      console.log(toast);
      setTimeout(() => {
        toast.remove();
      }, 2000);
    }
  }
};
</script>

Toast.vue 单文件组件代码如下

<template>
  <div class="my-toast" v-if="show">
    <div :class="type">{{ message }}</div>
  </div>
</template>
<script>
export default {
  props: {
    message: {
      type: String,
      required: true
    },
    type: {
      type: String,
      default: "error"
    }
    show: {
      type: Boolean,
    }
  }
};
</script>
<style lang="less" scoped>
.my-toast {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 300px;
  border: 1px solid #ccc;
  text-align: center;
  .error {
    color: red;
  }
  .success {
    color: green;
  }
}
</style>

再进一步封装

上面的例子中,我们引入了 create.js 以及 对应的单文件组件。它还是不够简洁,我们可以再封装一次,只需要调用一个js就搞定。如果我们将它在main.js里面引入并挂载到vue实例属性,那么调用就非常方便了。

// 在main.js里注册实例属性
import showDialog from '@/views/jsDialog/index.js'
Vue.prototype.$showDialog = showDialog

// 其他地方直接使用 this.$showDialog(options) 即可调用组件

下面来看看实现思路,关于render函数createElement的options的配置,参见 createElement 参数 - 深入数据对象

// showDialog/index.js
import Vue from "vue";
import DialogComponent from '@/views/jsDialog/src/index.vue'

let TheDialog = null
export default function showDialog(options) {
  // 如果未移除,先移除
  TheDialog && TheDialog.remove()

  TheDialog = create(DialogComponent, {
    on: {
      // 单文件组件内部可以emit该事件,销毁TheDialog组件
      'close-dialog': () => {
        TheDialog.remove()
      }
    },
    props: {
      // 需要传入的属性,单文件组件需要使用props接收
      title: '标题',
      content: '内容'
    }
    // 其他参数
    ...options
  })

  function create(Component, options) {
    // 先创建实例
    const vm = new Vue({
      render(h) {
        // h就是createElement,它返回VNode
        return h(Component, options);
      }
    }).$mount();

    // 手动挂载
    document.body.appendChild(vm.$el);

    // 销毁方法
    const comp = vm.$children[0];
    comp.remove = function() {
      document.body.removeChild(vm.$el);
      vm.$destroy();
    };
    return comp;
  }
}

注意,虽然js调用更方便了,但js处理、render组件的传参复杂度会增加。它和普通组件各有各的优缺点。

参考

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant