vue2如何实现响应式

发布订阅模式+数据双向绑定=基本的响应式

实现的逻辑

// 订阅器模型
let Dep = {
    // 订阅者列表
    clientList: {},
    // 订阅函数
    listen: function (key, fn) {
        (this.clientList[key] || (this.clientList[key] = [])).push(fn)
    },
    // 触发函数
    trigger: function () {
        let key = Array.prototype.shift.call(arguments),
            fns = this.clientList[key]
        if (!fns || fns.length === 0) return false
        for (let i = 0, fn; fn = fns[i++];) {
            fn.apply(this, arguments)
        }
    },
}
// 数据劫持
/**
 *
 * @param {data} 数据
 * @param {tag} 目标
 * @param {dataKey} 数据key
 * @param {selector} 显示的dom
 */
let dataHi = ({ data, tag, dataKey, selector }) => {
    let value = '',
        el = document.querySelector(selector)
    Object.defineProperty(data, dataKey, {
        get() {
            return value
        },
        set(val) {
            value = val
            Dep.trigger(tag, val)
        }
    })
    // 订阅
    Dep.listen(tag, val => {
        el.innerHTML = val
    })
}

引用的实例

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>vue2响应式原理</title>
    <script src="./index.js"></script>
  </head>
  <body>
    <div>数据响应</div>
    <div class="box-1"></div>
    <div class="box-2"></div>
    <script>
      let obj = {};
      dataHi({
        data: obj,
        tag: "view-1",
        dataKey: "one",
        selector: ".box-1",
      });
      dataHi({
        data: obj,
        tag: "view-2",
        dataKey: "two",
        selector: ".box-2",
      });
      obj.one = "3333";
      obj.two = "55555";
    </script>
  </body>
</html>

vue3响应式实现

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>vue3实现响应式</title>
  </head>
  <body>
    <script>
      let obj = {
        name: "小明",
        age: 18,
      };
      // 代理
      const p = new Proxy(obj, {
        // 读取
        get(target, key) {
          console.log(`读取p的属性${key}`);
          return target[key];
        },
        // 设置
        set(target, key, value) {
          target[key] = value;
          console.log(`设置p的属性${key}`);
          return true;
        },
        // 移除属性
        deleteProperty(target, key) {
          return delete target[key];
        },
      });
    </script>
  </body>
</html>

渲染函数render()

一值多判断的情况下使用

<script>
export default {
  props: {
    type: {
      type: String,
      default: "primary",
    },
    text: {
      type: String,
      default: "primary",
    },
  },
  // h => 原生js createElement()=> 创建真实元素 => 生成虚拟dom => 真实dom
  render(h) {
    return h(
      "button",
      {
        class: "btn btn-" + this.type,
        domProps: {},
      },
      this.text
    );
  },
};
</script>

<style scoped>
.btn {
  width: 100px;
  height: 40px;
  background-color: #f5f5f5;
}
.btn-success{
    background-color: #67C23A;
}
</style>

组件自动注册

实现的思路如下

  1. 将文件名首字母转大写
  2. 通过webpack内置方法获取所有的组件文件夹下的.vue文件
  3. 通过循环获取到当前的组件和组件的名称
  4. 通过Vue.component注册组件
// global.js
import Vue from 'vue'
// 首字母大写
const changeString = (str) => {
    return str.charAt(0).toUpperCase() + str.slice(1);
}
// 动态引入文件
export const initComponent = () => {
    /**
     * 1、当前路径
     * 2、是否匹配子集
     * 3、匹配文件格式
     */
    const requireContent = require.context('./', false, /\.vue$/i);
    const componentsList = requireContent.keys() || [];
    componentsList.forEach(fileName => {
        // 当前组件
        const currentComponent = requireContent(fileName)
        // 截取当前组件的名称
        const currentComponentName = changeString(fileName.replace(/^\.\//, '').replace(/\.\w+$/, ''))
        // 组册组件
        Vue.component(currentComponentName, currentComponent.default || currentComponent)
    });
};

在项目入口main.js文件引入

// main.js
import {initComponent} from './components/common/global'
initComponent()

自定义指令实现控制按钮权限

通过按钮传入权限值,判断这个值是不是在权限列表里面,如果不在通过指令移除这个按钮

具体实现的代码例子如下:

// utils/Authority.js
import Vue from 'vue'
const checkAuth = (key) => {
    // 假设这个是权限数组
    let arr = ['1', '2', '3', '4', '5', '6']
    let index = arr.indexOf(key)
    if (index > -1) {
        return true
    } else {
        return false
    }
};
export const authBtn = () => {
    Vue.directive('display-key', {
        inserted(el, binding) {
            // 指令值
            let displayKey = binding.value
            if (displayKey) {
                // 权限列表包含这个指令值
                let hasKey = checkAuth(displayKey)
                if (!hasKey) {
                    // 如果没有这个权限,就移除这个元素
                    el.parentNode && el.parentNode.removeChild(el)
                }
            } else {
                throw new Error('请传入key')
            }
        }
    })
}
// main.js
import { authBtn } from './utils/Authority'
authBtn()

侦听整个对象的变化

演示的html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>侦测整个对象</title>
  </head>
  <body>
    <script type="module">
      import { Observer } from "./Observer.js";
      let obj = new Observer({
        name: "张三",
        age: 18,
      });
      obj.value.age = 18888;
      obj.value.h = {};
      obj.value.h.a = 999
    </script>
  </body>
</html>

Observer.js

// Observer.js

export class Observer {
    constructor(value) {
        this.value = value;
        if (Array.isArray(value)) {
            // 执行数组逻辑
        } else {
            // 执行对象逻辑
            this.walk(value);
        }
    }
    walk(obj) {
        const keys = Object.keys(obj);
        for (let i = 0; i < keys.length; i++) {
            defineReactive(obj, keys[i])
        }

    }
}
function defineReactive(obj, key, value) {
    if (arguments.length === 2) {
        value = obj[key];
    }
    if (Object.prototype.toString.call(value) === '[object Object]') {
        new Observer(value);
    }
    Object.defineProperty(obj, key, {
        get() {
            console.log(`${key}属性被读取`);
            return value;
        },
        set(newValue) {
            if (newValue === value) return;
            value = newValue;
            console.log(`${key}属性被修改为${newValue}`);
        },
    });
}
打赏作者
微信
支付宝
返回顶部