codingories

codingories 查看完整档案

上海编辑上海商学院  |  电子商务 编辑思懿电子  |  前端工程师 编辑 www.zjj21.com 编辑
编辑

不想当制作人的rapper不是好程序员!

个人动态

codingories 发布了文章 · 9月16日

高级前端养成40js专精08之继承和组合

  1. 为什么要有类
  • 不同对象的属性重复了,就有了类
  • 为什么要有继承

    • 不同的类的属性重复了,就有了继承
  • 大部分编程技巧都是为了解决重复
  1. 两个对象的属性重复了
let person1 = {
  name: "ories",
  age: 18,
  sayHi() {},
};
let person2 = {
  name: "jack",
  age: 23,
  sayHi() {},
};
  • 于是就有了类和构造函数
class Person {
  name;
  age;
  sayHi() {}
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}
let person1 = new Person("ories", 18);
let person2 = new Person("jack", 23);
  • 我们发现 js 的 class 特别麻烦,声明 name,写了四次,于是改写 TypeScript
class Person{
  sayHi(): void {} // sayHi 的返回值为空
  constructor(public name: string, public age: number){}
  // 由于name前面有public,所以name会变为this.name
}
let person1 = new Person('ories', 18)
let person2 = new Person('jack', 23)
  • 细节

    • name 和 age 怎么赋值的
    • 类型怎么写的
    • 如何运行上面代码, ts-node
  • 更加配置化运行 TypeScript

    • 加入 tsconfig.json
    • 常见配置 compilerOptions/noImplicitAny:true,禁用隐式的 any 类型
  1. 总结
    • 类就是把对象的属性提前写好,避免重复
    • 类里面的字段会编程对象的属性
    • 为了节约内存,所有函数<font color=orange>都是共用的</font>(存疑 1)

      • person1.sayHi === person2.sayHi
    • 而<font color=orange>非函数属性是各个对象自有的</font>(存疑 2)
    • 使用 <font color=skyblue>console.dir(person1)</font>可以看出来
  • 构造函数

    • 属性名虽然可以提前写好,但是属性值不行
    • 所以需要构造函数接收函数,初始化属性值
    • 构造函数不需要写 return,默认会 return 新对象,即 constructor 默认 return this
  • 语法

    • JS 的所有语法可以在 MDN 查看
    • TS 的所有语法可以在 TS 英文/中文官网查看
    • 学会举一反三,不要妄图系统掌握
  1. 函数都是共用的?
  • 给每个对象自身加个函数行不
class Person {
  mySayhi = () => {}; // 自用
  sayHi(): void {} // 共用
}
let person1 = new Person();
let person2 = new Person();
console.dir(person1);
console.dir(person2);
  • 这玩意儿除了浪费内存还有啥用

    • React 里面非常有用
import React from "react";
import ReactDOM from "react-dom";

class App extends React.Component {
  name = "Frank";
  sayHi = () => {
    console.log(this);
    console.log(`Hi, I'm ${this.name}`);
  };
  render() {
    return (
      <div>
        <button onClick={this.sayHi}>say hi</button>
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
  1. 非函数属性是各个对象自有的
  • 创建一个共用的属性行吗
Person.prototype.kind = "人类";
let person1 = new Person();
let person2 = new Person();
person1.kind === person2.kind;
// 不用原型做不到(class 做不到)
  • 但是可以在 class 身上加属性
class Person {
  static kind = "人类";
}
console.log(Person.kind); // 人类
  1. 小结
    • 声明对象的自身属性(非函数)
    • 声明对象的共有函数
    • 声明对象的自身函数
    • 声明类的自身属性(可以是函数)
  1. 继承
  • 现在要给 Person 添加功能

    • person1.on('dire',fn)
    • person1.emit('die')
    • person1.off('die',fn)
    • 让 Person 实例具有发布订阅功能,怎么做?
  1. 加代码
class Person {
  constructor() {}
  sayHi() {}
  cache = [];
  on() {}
  off() {}
  emit() {}
}
let person1 = new Person();
// 这样person1 就既是人类,有能发布订阅
  • 除了人类,还有另一个类:报社
class 报社{
  constructor(public name){
    print(){}
    cache = []
    on(){}
    off(){}
    emit(){}
  }
}
// 这样 报社1 就既是报社,又能发布订阅
  • 消除重复

    • Person 和报社有重复属性

      • 把重复属性抽出来,单独写一个类 EventEmitter
      • 然后让 Person 和报社继承 EventEmitter
    • 细节

      • constructor 要调用 super()
      • 以保证 EventEmitter 实例被初始化
  • 继承
class EventEmitter {
  constructor(){}
  cache = []
  on(){}
  off(){}
  emit(){}
}
class Person extends EventEmitter{
  constructor(public name){
    super() // super的作用是调用EventEmitter中的constructor
  }
  sayHi(){}
}
class 报社 extends EventEmitter{
  constructor(public name){
    super()
  }
  print(){}
}
  1. 继承的其他功能
  • 重写

    • 子类重写父类的所有属性,以实现多态
    • 多态的意思是不同的子类对同一个消息有不同的反应
class Person extends EventEmitter{
  constructor(public name){ super() }
  sayHi(){}
  on(eventName, fn){
    console.log('我要监听啦')
    super.on(eventName, fn)
  }
}
  1. 继承有什么问题?
  • 如果我需要更多功能怎么办?
  • 两个选择

    • 让 EvenEmitter 继承其他类
    • 让 Person 继承两个类(多继承)
    • 两个方法都不好
  1. 组合
  • 组合并没有固定的写法,只演示一种写法
  • 让 Person 实现发布订阅
class Person {
  eventEmitter = new EventEmitter(); // 人类拥有事件发布功能
  name;
  sayHi() {}
  on(eventName, fn) {
    this.eventEmitter.on(eventName, fn);
  }
  off(eventName, fn) {
    this.eventEmitter.off(eventName, fn);
  }
  emit(eventName, data) {
    this.eventEmitter.emit(eventName, data);
  }
}
  • 优化代码
class Person {
  name;
  sayHi() {}
}
let person1 = new Person("frank");
mixin(person1, new EventEmitter()); // 把EventEmitter的on ,off, emit 拷贝到person1
function mixin(to, from) {
  for (let key in from) {
    to[key] = from[key];
  }
  // 注意,这是最简化的mixin,实际会复杂点
}
  • 实际上要这么写
function createEventEmitter() {
  const cache = [];
  return {
    on() {},
    off() {},
    emit() {},
  };
}
// 组合的意思是要什么复制什么,只把地址复制过去
  • 如果要更多功能
class Person {
  name;
  sayHi() {}
}
let person1 = new Person("ories");
mixin(person1, new EventEmitter());
mixin(person1, new Flyer());
mixin(person1, new Killer());
  1. 有了组合,可能不需要 class
  • 直接用函数+必包
  • 举例,猫,狗,动物
dog
  .wangwang()
  .poop();
cat
  .miaomiao()
  .poop();
animal // 动物父类
  .poop()
    dog // 狗子类
      .wangwang()
    cat // 猫子类
      .miaomiao()
  • 机器人
cleaningRobot
  .run()
  .clean()

animal
  .poop()
    dog.wangwang()
    cat.miaomiao()
robot // 机器人父类
  .run()
    murderRobot // 子类杀人机器人
      .kill()
    cleaningRobot // 子类清扫机器人
      .clean()
  • 碰到需求:狗形状各杀人机器人
robot
  .run()
    murderRobot
      .kill()
    cleaningRobot
      .clean()

animal
  .poop() // 这行不需要
    dog
      .wangwang() // 这行需要,所以通过继承搞出狗型杀人机器人基本是不可能的
    cat
      .miaomiao()
  • 于是用组合

    • dog = poop()+wangwang()
    • cat = poop()+miaomiao()
    • cleaningRobot = run()+clean()
    • murderRobot = run()+kill()
    • 狗型杀人机器人 = run()+kill()+wangwang()
  • 代码怎么写, 不用 class 写 dog
const createWang = (state) => ({
  wangwang: () => {
    console.log(`汪汪,我是${stage.name}`);
  },
});
const createRun = (state) => ({
  run: () => {
    state.position += 1;
  },
});
const createDog = (name) => {
  const state = { name, position: 0 }; // 这个state是一个闭包
  return Object.assign({}, createWang(state), createRun(state));
  // 空对象最后就剩下wangwang,run的地址,
  // {}是唯一暴露的出口
  // 对象是穷人的必包,java没有闭包只能用对象封装功能
  // 如果有闭包,就用对象,闭包,对象暴露api,api去操作这个对象
};
const dog = createDog("小白");
  1. 最早写前端的一批人由 java 转入,他们带来了 class 继承,多态
  • 前端写着写着发现代码越写越复杂
  • 后端有些数据结构相对固定,但是事实上前端的需求很灵活,
  • 这样子前端就开始想是不是面向对象的问题,继承为什么难用,一旦出现交叉就搞不过来
  • 所以往其他方向探索,一大分支就是函数式编程,以上就是函数来搞定
  1. 狗型杀人机器人怎么写
const createMurderRobotDog = (name) => {
  const state = { name, position: 0 };
  return Object.assign(
    {},
    createWang(state),
    createRun(state),
    createKill(stage)
  );
};
const murderRobotDog = createMurderRobotDog("小黑");
  1. 组合的总结
  • 组合的缺点,写法太灵活
  • 组合优于继承
  1. 从新再写一遍组合
  • 用 wangwang 和 poop 创造出 dog
// dog = poop + wangwang
const createPoop = (state) => ({
  // 这里不加括号会有bug,js会以为是代码块,不返回对象
  poop() {
    state.weight -= 1;
    console.log(`我在拉屎,体重变为${state.weight}`);
  },
});
p = createPoop({ weight: 100 });
p.poop();

const createWang = (state) => ({
  wangwang() {
    console.log(`汪汪,我叫${state.name}`);
  },
});
w = createWang({ name: "小白" });

const createDog = (name) => {
  const state = { name: name, weight: 100 };
  return Object.assign({}, createWang(state), createPoop(state));
};
dog = createDog("小白");
dog.wangwang();
dog.poop();
  1. 什么时候用继承
  • 场景

    • 开发者不会用组合
    • 功能没有太多交叉的地方,一眼就能看出继承关系
    • 前端库比较喜欢用继承
  • 举例

    • const vm = new Vue({...})
// 共有属性上有on emit off
// 为代码其就是
mixin(Vue.prototype, eventemitter) // 组合
  • Vue 混入了 EventEmitter
  • class App extends React.Component{...}
  • React.Component 内置了方便的复用代码(钩子)
  1. 什么时候用组合
  • 场景

    • 功能组合非常灵活,无法一眼看出继承关系
    • 插件系统,Vue 的插件就是将功能复制到 Vue 的原型
// Vue的源代码
// Vue.use 接受一个插件,或者对象
有一句installedPlugins.push(plugin) // 把插件安装到被安装的插件里
// 详情见Vue文档开发插件3,把created复制到当前实例
// 或者4.在Vue.prototype去添加属性
  • mixin 模式
源代码中的mergeOptions,其实就是复制属性到Vue的options
  • 举例

    • Vue.mixin()
    • Vue.use(MyPlugin)
    • React 接收组件的组件,由于 react 用函数,组件组合起来更方便
function App() {
  return <div className="App">{connect(A, B)}</div>;
}
function A(props) {
  return <div>我是A,我的儿子是{props.children}</div>;
}
function B(props) {
  return <div>我是B</div>;
}
function connect(Component1, component2) {
  return (
    <Fragment>
      <Component1></Component1> | <Component2 />
    </Fragment>
  );
}
  1. 总结
  • 如果开发基础库,用继承
  • 业务开发使用组合
  • 不一定对,需要灵活选择
  1. 组合更占内存么?
  • 通过将函数写在外面解决,会产生新的函数
const f1 = () => {};
createF1 = (state) => ({ f1: f1 });
const f2 = () => {};
createF2 = (state) => ({ f2: f2 });
createDog = (name) => {
  const state = { name };
  return Object.assign({}, createF1(), createF2());
};
dog1 = createDog(1);
dog2 = createDog(2);
dog1.f1 === dog2.f1;
  • 然而面向对象也会产生先的对象
  • 在原理上没太多区别
  1. 觉得对你有帮助的朋友,请关注公众号!
    qrCode.jpg
本文由博客一文多发平台 OpenWrite 发布!
查看原文

赞 2 收藏 2 评论 0

codingories 发布了文章 · 3月18日

(React全解)Class组件详解

一. class 组件创建方式

import React from 'react';

class B extends React.Component {
  constructor(props){
    super(props);
  }
  render(){
    return (
      <div>hi</div>
    )
  }
}

二. Props 外部数据

class Parent extends React.Component {
  constructor(props){
    super(props)
    this.state = {name:'frank'}
  }
  onClick = ()=>{}
  render(){
    return <B name={this.state.name} onClick={this.onClick}>hi</B>
  } // 这里的name和onClick就是props,来源是从this.state来的,是从外部来的作为props
}

// props如何初始化,这样子做了之后this.props就是外部数据的对象地址了
class B extends React.Component {
  constructor(props){
    super(props);
  }
  render(){

  }
}

// 读取props
class B extends React.Component {
  constructor(props){
    super(props);
  }
  render(){
    return <div onClick={this.props.onClick}>
      {this.props.name}
      <div>
        {this.props.children}
      </div>
    </div>
  }
}

// 不能去写props,外部数据应该由外部更新

三. componentWillReceiveProps 的作用

  • 接收外部数据,只能读不能写,外部呀数据由父组件传递
  • 接受外部函数,在恰当的时机调用该函数,该函数一般是父组件的函数
import React from 'react';

class App extends React.Component {
  constructor(props){
    super(props)
    this.state = {x : 1}
  }

  onClick = () => {
    this.setState({
      x: this.state.x + 1
    })
  }

  render(){
    return (
      <div className="App">
        App <button onClick={this.onClick}>+1</button>
        <B name={this.state.x}/>
      </div>
    );
  }
}

class B extends React.Component {
  UNSAFE_componentWillReceiveProps(newProps, nextContext) {
    console.log('旧的 props')
    console.log(this.props)
    console.log('props变化了')
    console.log('新的p props')
    console.log(newProps)
  }
  render(){
    return (
      <div>{this.props.name}</div>
    )
  }
}

export default App;

四. State & setState 内部数据

  • 初始化 State
class B extends React.Component{
  constructor(props){
    super(props);
    this.state = {
      user: {name:'frank', age:187}
    }
  }
  render(){

  }
}
  • setState 的两种方式,推荐写成函数的形式,一般就用第一个参数,还有第二个参数接受成功之后的回调函数,另外写 state 的时候会进行一级合并(shallow merge)
  onClick = () => {
    this.setState({
      x: this.state.x + 1
    })
    this.setState({
      x: this.state.x + 1
    })
  }

  onClick2 = () => {
    this.setState((state)=>({x:state.x+1}))
    this.setState((state)=>({x:state.x+1}))
  }

五. 生命周期

let div = document.createElement('div')
// 这是div的 create/construct 过程
div.textContent = 'hi'
// 这是初始化state
document.body.appendChild(div)
// 这是div的mount过程
div.textContent = 'hi2'
// 这是div的update过程
div.remove()
// 这是div的unmount过程
  • 函数列表,以下是常用的
constructor() - 这里初始化state
shouldComponentUpdate() - return false 阻止更新
render() - 创建虚拟DOM
componentDidMount() - 组件已出现在页面
componentDidUpdate() - 组件已更新
componentWillUnmount() - 组件将死
  • constructor的用途

    • 初始化props
    • 初始化state,但此时不能调用setState
    • 用来写bind this

        constructor(){
            this.onClick = this.onClick.bind(this)
        }
        可以用新语法代替
        onClick = ()=> {}
        constructor() {
      
        }
    • 可不屑
  • shouldComponentUpdate的用途

    • 返回true表示不阻止UI更新
    • 返回false表示阻止UI更新
    • 示例见下页
    • 面试常问: shouldComponentUpdate有什么用?
    • 答: 它允许我们手动判断是否要进行组件更新,我们可以根据应用场景灵活地设置返回值,以避免不必要的更新
  • React.PureComponent会对state和props进行浅对比,就不需要shouldComponentUpdate了
  • render的用途

    • 展示视图,
    // return 对应的对象是不是一个dom元素, 而是一个表示dom元素的对象, 我们称其虚拟dom 
    return (<div>...</div>)
    • 只能有一个根元素
    用React.Fragment做占位,也可以写成<></>,两者等价
    • 技巧

      // render里面可以写if...else
      // 可以写?:
      // 不能直接写for,需要用数组
      // 可以写array.map 
      
      import React from 'react';
      
      class App extends React.PureComponent {
        constructor(props){
          super(props)
          this.state = {
            n:1
          }
        }
        onClick= ()=>{
          this.setState(state=> ({
            n: state.n+1
          }))
        }
        
        render(){
          console.log('render了一次')
          let message
          if(this.state.n % 2 === 0 ) {
            message = <div>偶数</div>
          } else {
            message = <span>奇数</span>
          }
          return (
            <>
              {message}
              <button onClick={ this.onClick }>+1</button>
            </>
          )
        }
      }
      
      export default App;
      // 循环的写法
      render() {
        return this.state.array.map(n=><span key={n}>{n}</span>)
      }
    • componentDidMount()的用途

      • 在元素插入页面后执行代码,这些代码依赖DOM
      • 比如你想获取div的高度,就最好在这里写
      • 此处可以发起加载数据的AJAX请求(官方推荐)
      • 首次渲染会执行此钩子

        componentDidMount() {
        const div = document.getElementById('xxx')
        const {width} = div.getBoundingClientRect()
        // console.log(width)
        this.setState({width})
        }
        
        render(){
          return(
            <div id='xxx'>Hello World, {this.state.width}
            </div>
          )
        }
      • 也可以用ref来做

        import React from 'react';
        
        class App extends React.PureComponent {
          constructor(props){
            super(props)
            this.state = {
              n:1,
              width: undefined
            }
            this.divRef = React.createRef()
          }
          onClick= ()=>{
            this.setState(state=> ({
              n: state.n+1
            }))
          }
        
          componentDidMount() {
            const div = this.divRef.current
            const {width} = div.getBoundingClientRect()
            // console.log(width)
            this.setState({width})
          }
        
          render(){
            return(
              <div ref={this.divRef}>Hello World, {this.state.width}px
              </div>
            )
          }
        }
        
        export default App;
        
    • componentDidUpdate()的作用

      • 在视图更新后执行代码
      • 此处也可以发起AJAX请求,用于更新数据(看文档)
      • 首次渲染不会执行此钩子
      • 在此处setState可能会引起无限循环,除非放在if里
      • 若componentDidUpdate返回false,则不触发此钩子
      • componentWillUnmount的用途

        • 组件将要被移除页面然后被销毁时执行的代码
        • unmount过的组件不会再次mount
        • 举例:
        如果你在componentDidMount里面监听了window scroll
        那么就要在componentWillUnmount的用途里面取消监听
        如果你在componentDidMount里面创建了Timer
        那么就要在componentWillUnmount里面取消Timer
        如果你在componentDidMount里面创建了AJAX请求,这种情况比较特殊,数据还没回来你就死了 
        那么就要在componentWillUnmount里面取消请求
    • 总结:

      • 能用函数组件就用函数组件,能用purecomponent就用purecomponent,最后不行采用 react.component
      • 生命周期函数
        生命周期.png

最后,个人微信,欢迎交流,提供工作机会。

wechat0.jpg

查看原文

赞 0 收藏 0 评论 0

codingories 发布了文章 · 2月8日

旺财记事本(数据保存至LocalStorage)

1. 获取用户最新选择的 Tags

// Tags.Vue
this.$emit('xxx', this.selectedTags)
//Money.Vue
<Tags :data-source.sync="tags" @xxx="yyy"/>
yyy(zzz:string[]){
  console.log(zzz);
}
  • 改名:
// Tags.Vue
this.$emit('update:selected', this.selectedTags)
//Money.Vue
<Tags :data-source.sync="tags" @update:selected="onUpdateTags"/>
onUpdateTags(tags:string[]){
  console.log(tags);
}

2. 同理获取用户最新输入的 Notes,Types,NumberPad

// Money.vue
<Layout class-prefix="layout">
  <NumberPad @update:value="onUpdateAmout"/>
  <Types @update:value="onUpdateType"/>
  <Notes @update:value="onUpdateNotes"/>
  <Tags :data-source.sync="tags" @update:value="onUpdateTags"/>
</Layout>
export default class Money extends Vue{
    tags = ["衣","食","住","行"]

    onUpdateTags(value:string[]){
      console.log(value);
    }

    onUpdateNotes(value:string){
      console.log(value);
    }

    onUpdateType(value:string){
      console.log(value)
    }
    onUpdateAmout(value:string){
      console.log(value)
    }
  }

3. TS 中如何使用 Watch

  • Notes.vue 的 input 中每次用户输入都要向 Money.vue 更新数据
// Tag.vue
export default class Notes extends Vue{
  value = '';
  @Watch('value')
  onValueChanged(value:string ){
    this.$emit('update:value',value)
  }
}

4. TS 中的类型声明-将所有数据记录

type Record = {
  tags!: string[] // !表示可以没有这个属性
  notes: string
  type: string
  amount: number
}

record: Record = {
  tags: [],notes:'',type:'-',amount:0
}

5. 把值重新传回 types,方便维护

  • 如果你想给组件一个初始值,然后更新的时候时候,又要拿到最新的值,就用.sync
  //Types.Vue
  // 这样就不需要之前的watch
   export default class Types extends Vue {
    @Prop() readonly value!: string;
    selectType(value: string) {
      if (value !== "-" && value !== "+") {
        throw new Error("type is unknown")
      }
      this.$emit('update:value', value)
    }
  }
  // Money.Vue
    <Types :value="record.type" @update:value="onUpdateType"/>
  // 可以简写成
  <Types :value.sync="record.type" />

6. 解决 push 进数组数据一样的 bug

  • 此 bug 产生的原因,push 进去的实际上是一个内存地址
    push的bug.png
  • 解决的办法:深拷贝
    saveRecord(){
      const record2  = JSON.parse(JSON.stringify(this.record));
      this.recordList.push(record2)
      console.log(this.recordList)
    }

7. 解决增加时间引起的数据库版本的问题

  • 不要一下子升级到最新版,要一个个版本的升级。
    数据迁移.png
  if (version === "0.0.1") {
    // 数据库升级, 数据迁移
    recordList.forEach(record => {
      record.createdAt = new Date(2020, 0, 1);
    })
    // 保存数据
    window.localStorage.setItem("recordList", JSON.stringify(recordList))
  }
  window.localStorage.setItem("version", "0.0.2")

最后,个人微信,欢迎交流!

wechat0.jpg

查看原文

赞 0 收藏 0 评论 0

codingories 发布了文章 · 2月7日

旺财记账项目-Money.vue组件实现(下)

1. 实现TypeScript的Vue组件。

  • 以Types组件为例改写

    • JS写法

      <template>
        <div>
          <ul class="types">
            <li :class="type === '-' && 'selected'"
            @click="selectType('-')">支出</li>
            <li :class="type === '+' && 'selected'"
            @click="selectType('+')">收入</li>
          </ul>
        </div>
      </template>
      
      <script lang="js">
        export default {
          name: "Types",
          props: ['xxx'],
          data() {
            return {
              type: '-' // '-'表示支出.'+'表示收入
            }
          },
          mounted() {
            console.log(this.xxx)
          },
          methods: {
            selectType(type) { // type 只能是 '-' 和 '+' 中的一个
              if(type !== '-' && type !== '+'){
                throw new Error('type is unknown')
              }
              this.type = type
            }
          }
        }
      </script>
    • TS写法,用class组件写data methods 和生命周期

      1. 起手

          export default class Types extends Vue {
            
          }
      2. Component修饰符,在最上面加@Compo按下tab键,选择从vue-property-decorator引入

          import {Component} from "vue-property-decorator"
          @Component
          export default class Types extends Vue {
          
          }
      3. 必需制定参数type的类型

        selectType(type: string) { // type 只能是 '-' 和 '+' 中的一个
        
        }
      4. 代码总结

          import Vue from 'vue'
          import {Component} from "vue-property-decorator"
            @Component
            export default class Types extends Vue {
              type = '-'  // '-'表示支出.'+'表示收入
              selectType(type: string) { // type 只能是 '-' 和 '+' 中的一个
                if(type !== '-' && type !== '+'){
                  throw new Error('type is unknown')
                }
                this.type = type
              }
              created(){}
              mounted(){}
            }
    • props用法
      1 查看一个包的最新版本,在终端输入

        npm info typescript version

2. TS 组件 @Prop装饰器

  • 用法

        // 左边Number是运行时的检查,右边number是编译时的检查,编译时的检查会在写代码的时候就给提示
        @Prop(Number) xxx: number | undefined;
        // Prop 告诉 Vue xxx 不是 data 是 prop
        // Number 告诉 Vue xxx 运行时类型 是个 Number
        // xxx 属性名
        // number | undefined 就是 告诉 TS xxx 的编译时类型
        // 为什么前面的Number是大些后面是小写,见3.编译时和运行时的区别
  • TS的好处

    1. html直接空格告诉你有哪些参数
    2. 如果声明了类型,代码写错了会提前告诉,编译报错,无法编程js
    3. 检查调用的方法到底能不能调用,你不能写.tostring

3. 编译时和运行时的区别

运行时和编译时区别.png

4. TS的本质

TS的本质.png

5. Vue但文件组件的三种写法

  1. 用JS对象

      export default { data,props,methods,created,... }
  2. 用 TS 类

     <script lang="ts">
     @Component
     export default class XXX extends Vue{
       xxx: string = 'hi';
       @Prop(Number) xxx: number|undefined;
     }
  3. 用 JS 类

     @Component
     <script lang="js">
     export default class XXX extends Vue{
       xxx = 'hi'
     }

6. 开始实现NumberPad组件

  • 在按钮上绑定事件,不加参数会默认传入事件

    <button @click="inputContent">1</button>
    inputContent(event: MouseEvent){
      if(event.target){
        console.log(event.target.textContent);
      }
    }
  • 强制指定类型,需要这么做的原因是Vue和Typescript不够好

    inputContent(event: MouseEvent){
      if(event.target){
        const button = (event.target as HTMLButtonElement)
        console.log(button.textContent);
      }
    }
  • 如果只是要排除空,也可以这么写

    const input = button.textContent!;
  • 基本完成NumberPad,NumberPad 组件所有代码

    import Vue from "vue"
    import {Component, Prop} from "vue-property-decorator"
    
    @Component // 如果如果这样写,就表示Class就有一个propMessage的props,类型为String
    export default class NumberPad extends Vue {
      output: string = '0';
      inputContent(event: MouseEvent){
        const button = (event.target as HTMLButtonElement);
        const input = button.textContent!;
        if(this.output.length === 16){return}
        if(this.output === '0'){
          if ('0123456789'.indexOf(input) >= 0) {
            this.output = input
          } else {
            this.output += input;
          }
          return
        }
        if (this.output.indexOf(".") >= 0 && input === ".") {return;}
        this.output += input;
      }
    
      remove(){
        if (this.output.length === 1){
          this.output = '0';
        } else {
          this.output = this.output.slice(0, -1);
        }
      }
    
      clear(){
        this.output = '0';
      }
      ok(){
    
      }
    }
    

67. 开始实现备注功能

  • 原生html input实现绑定数据
<input type="text" :value="value" @input="onInput" placeholder="在这里输入备注">
onInput(event: KeyboardEvent){
      const input = event.target as HTMLInputElement;
      this.value = input.value
    }
  • 对:value 和 @input绑定数据进行简写
<input type="text" v-model="value" placeholder="在这里输入备注">

8. 开始实现tags模块

  • 只能不能改外部数据的写法

    @Prop() readonly dataSource: string[] | undefined // 加readonly
  • 内部不能直接改外部数据

    // 内部
    // this.dataSource.push(name!); // 不能改外部数据,这种写法不推荐!
    this.$emit("update:dataSource", [...this.dataSource, name])
    // 外部,用.sync简写
    <Tags :data-source.sync="tags"/>

最后,个人微信,欢迎交流!

wechat0.jpg

查看原文

赞 0 收藏 0 评论 0

codingories 发布了文章 · 2月6日

旺财记账项目-Money.vue组件实现(上)

1. 小技巧:快速包裹每一行

  • 选中,按两下 shift,输入 surround ,选择 surround with emit,输入 li*,意思就是 n 个 li 包住行数

2. input,label 的写法

  • 新手
<label for="xxx"></label>
<input id = 'xxx' type="text">
  • 老手
<label>
  <span>备注</span>
  <input type="text">
</label>

3. 文件行数规则

  • 一个文件超过 150 行,一般拆成多个文件。

4. 开始写 CSS

  • 1. css 重置,抽成另外一个文件 reset.scss,然后在 app.vue 中引入
// reset.scss
* {
  margin: 0; padding: 0;
  box-sizing: border-box;
}

a{
  text-decoration: none;
  color:inherit;
}

// app.vue
@import "~@/assets/style/reset.scss";
  • 2. 处理字体,需要用到 fonts.css
    • 在 google 搜索 "fonts.css" 中文
    • 找到 font-family 的地方,在 helper.scss 中声明 font,然后在 app.vue 中使用
// helper.scss
$font-hei:  -apple-system, "Noto Sans", "Helvetica Neue", Helvetica, "Nimbus Sans L", Arial, "Liberation Sans", "PingFang SC", "Hiragino Sans GB", "Noto Sans CJK SC", "Source Han Sans SC", "Source Han Sans CN", "Microsoft YaHei", "Wenquanyi Micro Hei", "WenQuanYi Zen Hei", "ST Heiti", SimHei, "WenQuanYi Zen Hei Sharp", sans-serif;
// app.vue
body{
  line-height: 1.5;
  font-family: $font-hei;
}
  • 3. 把变量都放到 helper.css 里面。
    • command+shift+f 进行全局搜索
    • helper.css 这个文件只能放变量,函数和 mixin,最终会消失的东西

4. 写css推荐顺序

  • 一般而言无所谓,如果比较复杂,推荐从里到外,因为里面最简单

5. scss的用法

  • 选择器里面
.tags {
  > .current {

  }
}
  • 选择器本身
.tags {
  &.current {

  }
}

6. 按钮字体样式不会继承

  • 在reset.scss中加入
button, input{
  font: inherit;
}

7. 字体居中解决

  • 第一种方式,lineheight和高度一样,这种情况用于只有一行字
  • 第二种方式,用flex

8. 字的下划线比字长

border-bottom: 1px solid;
padding: 0 4px;

9. selected下划线的做法

  • 避免用border-bottom来做,这种做法当class消失会有抖动,要用以下做法
&.selected::after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 4px;
  background: #333;
}

10. 处理没有的字体,在其后面加上一类字体。

// monospace就是等宽的编程字体类
font-family: Consolas, monospace;

11. 如果用了float

  • 父元素一定要用clearfix
.clearfix::after{
  content:'';
  display: block;
  clear:both;
}
  • 用scss的%,placeholder功能
// helper.scss
// placeholder
%x{
  &::after{
    content: '';
    clear: both;
    display: block;
  }
}
// 如何用
// money.vue
.buttons {
  @extend %x;
}

12. 两层内阴影的写法

box-shadow: inset 0 -5px 5px -5px fade_out(black, 0.5),
inset 0 5px 5px -5px fade_out(black, 0.5);

13. 多个地方统一替换

  • 选中之后按command+r

14. 用前缀控制组件内css样式

// 这样写会有bug,要写deep

// Money.vue
<Layout class-prefix="layout">
</Layout>
<style lang="scss">
  .layout-content{
    border: 3px solid red;
  }
  .layout-wrapper{
    border: 3px solid blue;
  }
</style>

// Layout.vue
<template>
  <div class="layout-wrapper" :class="classPrefix && `${classPrefix}-wrapper`">
    <div class="content" :class="classPrefix && `${classPrefix}-content`">
      <slot />
    </div>
    <Nav/>
  </div>
</template>

<script lang="ts">
  export default {
    props: ['classPrefix'],
    name: "Layout"
  }
</script>

15. 如何模块化

  • 坚持一个文件查过150行就执行模块化
  • 把html scss剪切的单独.vue文件,引入的时候写<name 会总动引入
  • 最后把相关文件放到一个文件夹
查看原文

赞 0 收藏 0 评论 0

codingories 发布了文章 · 2月5日

svg的两个坑

1. WebStorm 按两下空格输入 soft wrap 进行换行

2. 第一个坑: svg 中的 fill 属性处理

  • svg 里面如果有 fill="",就会有颜色,可以手动去除,但是如果有很多个 svg 那么一个个去很麻烦
  • 用 svgo-loader 解决这个问题,在 vue.config.js 中配置
config.module
.use('svgo-loader').loader('svgo-loader')
.tap(options=>({
  ...options, plugins:[{
      removeAttrs:{attrs:'fill'}
    }]
  })
)
.end()
  • 安装方法
yarn add svgo-loader -D

3. 碰到问题安装包和 node 版本不匹配

  • The engine "node" is incompatible with this module. Expected version "^8.16.0 || ^10.6.0 || >=11.0.0". Got "8.12.0"
  • 解决办法用 nvm 安装 10 版本的,升级办法参考:https://dev.to/peterwitham/in...
nvm install 10

4. 提交修改

  • 点 Version Control->左侧飞机 icon

5. 统一管理 SCSS 变量

  • 在 assets 下新建 style/helper.scss,在用的地方进行引用
  • @import "~@/assets/style/helper.scss"

6. 第二个坑:引用 scss 出现下划线

  • 在 settings 里面搜索 webpack,添加 webpack configuration file 路径
node_modules/@vue/cli-service/webpack.config.js
  • 但是还没有解决,问题处在 svg-sprite-loader 有 bug,而 WebStorm 会进行提示,其实已经有人改了但是管理这个仓库的 jetBrains 没有合并,自己 folk 以下合并下 commit,再重新发布成 svg-sprite-loader-mod
  • 安装 svg-sprite-loader-mod 并且把原来的 svg-sprite-loader 删除
  • vue.config.js 中 svg-sprite-loader 的地方改成 svg-sprite-loader-mod

7. 如果有人问你遇到的最难的技术问题是什么?

  • 可以讲如何解决以上这两个问题,第二个问题过程更复杂。整个过程图如下图:
    svg下划线问题.png

最后,个人微信,欢迎交流!

wechat0.jpg

查看原文

赞 0 收藏 0 评论 0

codingories 发布了文章 · 2月2日

Vue造轮子简易版(阶段性总结)

1. 课前水平自测

  • 要对 JS,CSS,SVG 非常了解才能造 UI 轮子

2. 没有需求就不要写代码,没有设计稿也不要写代码

  • 没有设计稿就问设计要,不给就闹,把事情闹的越大越好,只要不写代码做什么都是对的

3. 单元测试是重构的前提

  • 单元测试能保证大概率没有 Bug

4. 对于工具的使用,学 ES/SCSS/Webpack 能用就行

5. 设计模式

  • 发布订阅模式,tabs 组件里的 eventBus,emit/on/off,发布订阅模式一定要有发布者,订阅者和事件中心
  • 单项数据流-tabs 组件,collapse,收到数据更新,自己不改通知父亲改,这改 React 奠定了基础,越是复杂的数据,越是要用单向数据流
  • 正交,两个 props 不能相互影响,也就是说所有的属性不要控制同一个东西。

    • 判断是否正交的依据在于,一个属性任意的改会不会影响另一个属性。
  • 可测试代码

    • 添加属性为了测试的时候选中这个属性,比如添加:data-name="name"
  • 不要让人思考

    • 即使是一行也要抽出来,作为一个函数。

6. 面向离职写代码的好处

  • 所有东西文档化
  • 被观察感->严格要求
  • 团队中表现优异, 写完轮子不给升职加薪就好走了

最后,个人微信,欢迎交流!

wechat0.jpg

查看原文

赞 0 收藏 0 评论 0

codingories 发布了文章 · 2月1日

Vue造轮子-手风琴组件

一. 大致的使用方法

<div id="app" style="padding-left: 100px">
  <g-collpase>
    <g-collapse-item title="标题1">内容1</g-collapse-item>
    <g-collapse-item title="标题2">内容2</g-collapse-item>
    <g-collapse-item title="标题3">内容3</g-collapse-item>
  </g-collpase>
</div>

二. 完成最外部的样式

//collapse
<style lang="scss" scoped>
  $grey: #ddd;
  $border-radius: 4px;
  .collapse {
    border: 1px solid $grey;
    border-radius: $border-radius;
  }
</style>

//collapse-item.vue
<style lang="scss" scoped>
  $grey: #ddd;
  $border-radius: 4px;
  .collapseItem {
    > .title {
      border: 1px solid $grey;
      margin-top: -1px;
      margin-left: -1px;
      margin-right: -1px;
    }
    &:first-child {
      > .title {
        border-top-left-radius: $border-radius;
        border-top-right-radius: $border-radius;
      }
    }
  }
</style>

三. 进一步调节样式

// collapse-item
<style lang="scss" scoped>
  $grey: #ddd;
  $border-radius: 4px;
  .collapseItem {
    > .title {
      border: 1px solid $grey;
      margin-top: -1px;
      margin-left: -1px;
      margin-right: -1px;
      min-height: 32px;
      display: flex;
      align-items: center;
      padding: 0 8px;
    }
    &:first-child {
      > .title {
        border-top-left-radius: $border-radius;
        border-top-right-radius: $border-radius;
      }
    }
    > .content{
      padding: 8px;
    }
  }
</style>

四. 默认内容折叠起来和点击切换,基本样式和基本功能完成

// collapse-item
    data (){
      return {
        open: false
      }
    }

    <div class="content" v-if="open" @click="open=!open">
      <slot></slot>
    </div>

五. 完善功能第一个点开,第二个就关闭

  • 因为结构比较简单,就父子两个组件,可以用父子通信来做。
    mounted(){
      this.eventBus.$on('update:selected', (vm)=>{
        if (vm !== this){
          this.close()
        }
      })
    },
    methods:{
      toggle(){
        if(this.open) {
          this.open = false
        }else{
          this.open = true
          this.eventBus.$emit('update:selected', this)
        }
      },
      close(){
        this.close()
      }
    }

六. 增加功能是否可以选择多个

  • 方案一.用 single 变量是否需要控制 eventBus
    // 添加single选项,有single就注入,没有就不注入
    // 但是这种方式不太完美会有警告
    props: {
      single: {
        type: Boolean,
        default: false
      }
    },
    provide() {
      if(this.single){
        return {
          eventBus: this.eventBus
        }
      }
    }

七. 可以设置默认 selected

// index.html

    <div id="app" style="padding: 100px">
      <g-collapse selected="2">
        <g-collapse-item title="标题1" name="1">内容1</g-collapse-item>
        <g-collapse-item title="标题2" name="2">内容2</g-collapse-item>
        <g-collapse-Item title="标题3" name="3">内容3</g-collapse-Item>
      </g-collapse>
    </div>

// collapse-item.vue
    mounted(){
      this.eventBus && this.eventBus.$on('update:selected', (name)=>{
        if (name !== this.name){
          this.close()
        }else{
          this.show()
        }
      })
    },

八. 回头解决子元素是否可以多个打开

  • 通过 collapse.vue 传给 collapse-item
// index.js
  <div id="app" style="padding: 100px">
    <g-collapse :selected="selectedTab" :selected.sync="selectedTab" single>
      <g-collapse-item title="标题1" name="1">内容1 Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus consequatur </g-collapse-item>
      <g-collapse-item title="标题2" name="2">内容2 Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid cupiditate dolore d! </g-collapse-item>
      <g-collapse-Item title="标题3" name="3"> 内容3 Lorem ipsum dolor sit amet, consectetur adipisicing elit. Excepturi, magnam. </g-collapse-Item>
      {{selectedTab}}
    </g-collapse>
  </div>
// collapse.vue
 mounted(){
      this.eventBus.$emit('update:selected',this.selected)
      this.eventBus.$on('update:selected', (name)=>{
        this.$emit('update:selected',name)
      })
      this.$children.forEach((vm)=>{
        vm.single = this.single
        console.log(vm)
      })
    }
// collapse-item.vue
  mounted(){
      this.eventBus && this.eventBus.$on('update:selected', (name)=>{
        if (name !== this.name){
          if(this.single){
            this.close()
          }
        }else{
          this.show()
        }
      })
    },

9.进一步修改,把选中的值改为数组

    // collapse.vue
    mounted(){
      this.eventBus.$emit('update:selected',this.selected)
      this.eventBus.$on('update:addSelected', (name)=>{
        this.selected.push(name)
        this.eventBus.$emit('update:selected',this.selected)  // 通知儿子
        this.$emit('update:selected',this.selected) // 通知外面
      })
      this.eventBus.$on('update:removeSelected', (name)=>{
        let index = this.selected.indexOf(name)
        this.selected.splice(index,1)
        this.eventBus.$emit('update:selected',this.selected)
        this.$emit('update:selected',this.selected)
      })
      this.$children.forEach((vm)=>{
        vm.single = this.single
        console.log(vm)
      })
    }
    // collapse-item.vue
    methods:{
      toggle(){
        if(this.open) {
          this.open = false
          this.eventBus && this.eventBus.$emit('update:removeSelected', this.name)
          // 移除一个被选中的东西
        }else{
          this.eventBus && this.eventBus.$emit('update:addSelected', this.name)
        }
      },
    }

10. 将所有数据流让父组件统一管理。

  • 不能直接操作 props 要先深拷贝
  // collapse.vue
      mounted(){
      this.eventBus.$emit('update:selected',this.selected)
      this.eventBus.$on('update:addSelected', (name)=>{
        let selectedCopy = JSON.parse(JSON.stringify(this.selected))
        if(this.single){
          selectedCopy = [name]
        }else{
          selectedCopy.push(name)
        }
        this.eventBus.$emit('update:selected',selectedCopy)  // 通知儿子
        this.$emit('update:selected',selectedCopy) // 通知外面
      })
      this.eventBus.$on('update:removeSelected', (name)=>{
        let selectedCopy = JSON.parse(JSON.stringify(this.selected))
        let index = selectedCopy.indexOf(name)
        selectedCopy.splice(index,1)
        this.eventBus.$emit('update:selected',selectedCopy)
        this.$emit('update:selected',selectedCopy)
      })
      this.$children.forEach((vm)=>{
        vm.single = this.single
        console.log(vm)
      })
    }

11. 数据流的核心

  • 不要出现两个东西互相让对方更新的状态
  // collapse.vue 爸爸
      mounted(){
      this.eventBus.$emit('update:selected',this.selected) // 一开始就通知所有儿子,该选中就选中
      this.eventBus.$on('update:addSelected', (name)=>{
        let selectedCopy = JSON.parse(JSON.stringify(this.selected))
        // 如果用户添加一个我就把selected拷贝一份,因为vue不支持直接修改props
        if(this.single){
          selectedCopy = [name]
        }else{
          selectedCopy.push(name)
        }
        this.eventBus.$emit('update:selected',selectedCopy)  // 得到最新被选中的item之后,通知儿子
        this.$emit('update:selected',selectedCopy) // 通知外面
      })

      this.eventBus.$on('update:removeSelected', (name)=>{
        let selectedCopy = JSON.parse(JSON.stringify(this.selected))
        let index = selectedCopy.indexOf(name)
        selectedCopy.splice(index,1)
        this.eventBus.$emit('update:selected',selectedCopy) // 如果用户想移除,也通知他儿子该移除就移除
        this.$emit('update:selected',selectedCopy)
      })
      this.$children.forEach((vm)=>{
        vm.single = this.single
        console.log(vm)
      })
    }¡
    // collapse-item.vue 儿子
        mounted(){
      this.eventBus && this.eventBus.$on('update:selected', (names)=>{
        // 监听eventBus,只要他爸爸要说更新,他就更新
        console.log(names)
        if (names.indexOf(this.name )>= 0){
          this.open = true
        }else{
            this.open = false
        }
      })
    },
    methods:{
      toggle(){
        if(this.open) {
          // 这里也没有修改自己的OPEN,而是在mounted中等爸爸通知我们修改open,所以他的open永远是爸爸在操作,儿子不操作
          this.eventBus &&this.eventBus.$emit('update:removeSelected', this.name)
          // 他自己触发一个意图,打算移除一个更新
          // 移除一个被选中的东西
        }else{
          this.eventBus && this.eventBus.$emit('update:addSelected', this.name)
          // 他自己触发一个意图,打算添加一个更新
        }
      },
    }

最后,个人微信,欢迎交流!

wechat0.jpg

查看原文

赞 1 收藏 1 评论 0

codingories 发布了文章 · 1月31日

popover组件测试用例

1. 解决小三角形引起的抖动问题。

    // 以一个为例
    &.position-bottom {
    margin-top: 10px;
    &::before, &::after{
      left: 10px;
    }
    &::before {
      border-top: none;
      border-bottom-color: black;
      /*border-color: black;*/
      bottom: 100%;
    }
    &::after {
      border-top: none;
      border-bottom-color: white;
      /*border-color: black;*/
      bottom: calc(100% - 1px);
    }
  }

2. 实现关闭功能,用 slot-scope,把 close 传给插槽

    // popover.vue
    // 第一步在slot上传一个属性
    <slot name="content" :close="close"></slot>

    // 第二步结构一下close,然后用就可以了
    // index.html
    <g-popover position="bottom">
      <template slot="content" slot-scope="{close}">
        <div><g-button @click="close">关闭</g-button></div>
      </template>
      <g-button>点我1</g-button>
    </g-popover>

3. popover 的第一个测试用例-position

it('可以设置position.', (done) => {
    Vue.component('g-popover', Popover)
    const div = document.createElement('div')
    document.body.appendChild(div)
    div.innerHTML = `
    <g-popover position="bottom" ref="a">
      <template slot="content">
        弹出内容
      </template>
      <button>点我</button>
    </g-popover>
    `
    const vm = new Vue({ // 这个vm不是组件,这个vm是一个div对应的实例,div里面才是popover组件
      el: div
    })
    vm.$el.querySelector('button').click()
    vm.$nextTick(() => {
      const {contentWrapper} = vm.$refs.a.$refs
      expect(contentWrapper.classList.contains('position-top')).to.be.false
      done()
    })
  })

4. 测试 mouseEnter 事件

  • 先确认用 dispatchEvent 的方式是可以触发 hover 事件的
    <body>
      <div id=xxx>
        hihihi
      </div>
      <button style="marin-top:20px;" id=button>click me</button>
    </body>

    xxx.addEventListener('mouseenter',function(){
      console.log('hi')
    })

    button.onclick=function(){
      let event = new Event('mouseenter');
      xxx.dispatchEvent(event)
    }
  • 可以设置 trigger 的测试用例有问题,先跳过,it 前面+一个 x 可以跳过

    xit('可以设置 trigger.', (done) => {
      Vue.component('g-popover', Popover)
      const div = document.createElement('div')
      document.body.appendChild(div)
      div.innerHTML = `
      <g-popover trigger="hover" ref="a">
        <template slot="content">
          弹出内容
        </template>
        <button>点我</button>
      </g-popover>
      `
      const vm = new Vue({ // 这个vm不是组件,这个vm是一个div对应的实例,div里面才是popover组件
        el: div
      })
      let event = new Event('mouseenter');
      vm.$el.dispatchEvent(event)
      vm.$nextTick(() => {
        const {contentWrapper} = vm.$refs.a.$refs
        expect(contentWrapper).to.be.exist
        done()
      })
    })
    

最后,个人微信,欢迎交流!

wechat0.jpg

查看原文

赞 0 收藏 0 评论 0

codingories 发布了文章 · 1月30日

Vue造轮子-popover组件(下)


title: Vue造轮子-popover组件(下)
date: 2020-01-29 10:37:44

tags: 前端学习 造轮子

1. 上一次的问题总结。

  • overflow:hidden -> body.appendChild
  • 关闭重复 -> 分开,document只管外面,popover只管里面
  • 忘了取消监听document -> 收拢close

2. 可以把一个函数哟没有五行作为一个优化的标准,简称为五行定律

3. 接下来把样式改好点

  .content-wrapper { // 如果写了scoped,popover里面那么就只作用于popover里面,移到外面就在外面了就可以
    position: absolute;
    border: 1px solid  $border-color;
    border-radius: $border-radius;
    filter: drop-shadow(0 0 1px rgba(0,0,0,0.5));
    /*通过drop-shadow解决小三角没有阴影的问题,但是兼容性不好 */
    /*box-shadow: 0 0 3px rgba(0, 0, 0, 0.5);*/
    background: white;
    transform: translateY(-100%);
    margin-top: -10px;
    padding: .5em 1em;
    max-width: 20em;
    word-break: break-all;
    &::before ,  &::after{
      content: '';
      display: block;
      width: 0;
      height: 0;
      position: absolute;
      border: 10px solid transparent;
      left: 10px;
    }
    &::after{
      content: '';
      display: block;
      width: 0;
      height: 0;
      position: absolute;
      border: 10px solid transparent;
      left: 10px;
    }
    &::before {
      border-top-color: black;
      top: 100%;
    }
    &::after {
      border-top-color: white;
      top: calc(100% - 1px);
    }
  }

4. 增加功能:用户可以选择popover是上面下面左面右面

  // 先要给contentWrapper加一个类,传递进入位置信息
  <div ref="contentWrapper" class="content-wrapper" v-if="visible"
    :class="{[`position-${position}`]:true}">
  </div>  

  // 获得位置信息     
  props: {
      position:{
        type: String,
        default: 'top',
        validator(value){
          return ['top','bottom','left','right'].indexOf(value) >= 0
        }
      }
    },
  }

  // 在method里面写方法,主要控制大概位置
  method: {
    positionContent() {
        const {contentWrapper, triggerWrapper} = this['$refs']
        document.body.appendChild(contentWrapper)
        let {width, height, top, left} = triggerWrapper.getBoundingClientRect()
        if(this.position === 'top'){
          contentWrapper.style.left = left + window.scrollX + "px"
          contentWrapper.style.top = top + window.scrollY + "px"
        }else if(this.position === 'bottom'){
          contentWrapper.style.left = left + window.scrollX + "px"
          contentWrapper.style.top = top + height + window.scrollY + "px"
        }else if(this.position === 'left'){
          contentWrapper.style.left = left + window.scrollX + "px"
          let {height: height2} = contentWrapper.getBoundingClientRect()
          contentWrapper.style.top = top  + window.scrollY + (height - height2)/2 + "px"
        }else if(this.position === 'right'){
          contentWrapper.style.left = left + window.scrollX + width + "px"
          let {height: height2} = contentWrapper.getBoundingClientRect()
          contentWrapper.style.top = top  + window.scrollY + (height - height2)/2 + "px"
        }
      },
  }

  // 然后对css进行处理,主要是箭头当想
  <style lang="scss" scoped>
  $border-color: #333;
  $border-radius: 4px;
  .popover {
    display: inline-block;
    vertical-align: top;
    position: relative;
  }
  .content-wrapper { // 如果写了scoped,popover里面那么就只作用于popover里面,移到外面就在外面了就可以
    position: absolute;
    border: 1px solid  $border-color;
    border-radius: $border-radius;
    filter: drop-shadow(0 0 1px rgba(0,0,0,0.5));
    /*通过drop-shadow解决小三角没有阴影的问题,但是兼容性不好 */
    /*box-shadow: 0 0 3px rgba(0, 0, 0, 0.5);*/
    background: white;
    padding: .5em 1em;
    max-width: 20em;
    word-break: break-all;
    &::before ,  &::after{
      content: '';
      display: block;
      border: 10px solid transparent;
      width: 0;
      height: 0;
      position: absolute;
    }

    &.position-top {
      transform: translateY(-100%);
      margin-top: -10px;
      &::before, &::after{
        left: 10px;
      }
      &::before {
        border-top-color: black;
        top: 100%;
      }
      &::after {
        border-top-color: white;
        top: calc(100% - 1px);
      }
    }

    &.position-bottom {
      margin-top: 10px;
      &::before, &::after{
        left: 10px;
      }
      &::before {
        border-bottom-color: black;
        bottom: 100%;
      }
      &::after {
        border-bottom-color: white;
        bottom: calc(100% - 1px);
      }
    }

    &.position-left{
      transform: translateX(-100%);
      margin-left: -10px;
      &::before, &::after{
        transform: translateY(-50%);
        top: 50%;
      }
      &::before {
        border-left-color: black;
        left: 100%;
      }
      &::after {
        border-left-color: white;
        left: calc(100% - 1px)
      }
    }

    &.position-right{
      margin-left: 10px;
      &::before, &::after{
        transform: translateY(-50%);
        top: 50%;
      }
      &::before {
        border-right-color: black;
        right: 100%;
      }
      &::after {
        border-right-color: white;
        right: calc(100% - 1px)
      }
    }

  }
</style>

最后,个人微信,欢迎交流!

wechat0.jpg

查看原文

赞 0 收藏 0 评论 0

认证与成就

  • 获得 3 次点赞
  • 获得 1 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 1 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2018-12-05
个人主页被 66 人浏览