The Future Depends on You
首页/Vue/实现一个 Vue 在线编辑器/
实现一个 Vue 在线编辑器
上次更新时间:2021-1-18 文章分类:Vue 阅读人数:14

1、预览

yctang

2、组件间通信方式

  1. props和$emit 父组件向子组件传递数据是通过prop传递的,子组件传递数据给父组件是通过$emit触发事件来做到的
  2. $attrs和$listeners A->B->C。Vue 2.4 开始提供了$attrs和$listeners来解决这个问题
  3. $parent,$children
  4. $refs 获取实例
  5. 父组件中通过provider来提供变量,然后在子组件中通过inject来注入变量。
  6. envetBus 平级组件数据传递 这种情况下可以使用中央事件总线的方式
  7. vuex状态管理

在线编辑器中,我们使用到了第一种和第四种方式。

3、实现

3.1、新建项目

使用 vue/cli 新建一个项目。

npm install -g @vue/cli
vue create vue-online-edit

? Check the features needed for your project:
(*) Babel
( ) TypeScript
( ) Progressive Web App (PWA) Support
( ) Router
( ) Vuex
>(*) CSS Pre-processors
( ) Linter / Formatter
( ) Unit Testing
( ) E2E Testing

? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default)
  Sass/SCSS (with dart-sass)
  Sass/SCSS (with node-sass)
  Less
> Stylus

3.2、编写 App.vue

在 APP.vue 中添加代码编辑组件和结果展示组件。

<template>
  <div id="app">
    <!-- 3.使用组件 -->
    <Edit @input="handleInput" @run="handleRun"></Edit>
    <Show :code="code" ref="show"></Show>
  </div>
</template>
<script>
// 1.声明组件并引入
import Edit from "@/components/edit.vue";
import Show from "@/components/show.vue";
export default {
  name: "App",
  // 2.组件的注册
  components: {
    Edit,
    Show,
  },
  data() {
    return { code: "" };
  },
  methods: {
    handleInput(code) {
      this.code = code;
    },
    handleRun() {
      this.$refs.show.run();
    },
  },
};
</script>

APP.vue 添加样式

<style lang="stylus" scoped>
* {
  margin: 0;
  padding: 0;
}

html, body, #app {
  width: 100%;
  height: 100vh;
}

#app {
  display: flex;
  justify-content: space-between;

  & > div {
    width: 49%;
    height: 100%;
  }
}
</style>

3.3、编写Edit组件

在 Edit.vue 中通过 $emit 触发 APP.vue 中的 handleInput 并将用户输入的代码传递过来。当点击 代码运行 时,Edit 通过 APP.vue 中 handleRun 方法的 this.$refs.show.run() 调用 Show 组件的 run方法解析用户输入的代码。

<template>
  <div class="edit">
    <div class="edit-btn">
      <button @click="$emit('run')">代码运行</button>
      <button @click="code = ''">清空代码</button>
    </div>
    <div class="edit-box">
      <textarea
        @input="handleInput"
        :value="code"
        @keydown.9.prevent="handleKeydown"
      ></textarea>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      code: "",
    };
  },
  methods: {
    handleInput(e) {
      this.code = e.target.value;
      this.$emit("input", this.code); //触发自己身上的事件
    },
    handleKeydown(e) {
      if (e.keyCode == 9) {
        this.code = e.target.value + "  ";
      }
    },
  },
};
</script>

Edit.vue 添加样式

<style lang="stylus">
.edit {
  .edit-btn {
    padding: 10px;
    background: #ccc;

    button {
      width: 80px;
      height: 40px;
      margin-right: 5px;
    }
  }

  .edit-box {
    width: 100%;
    height: 90%;
    box-sizing: border-box;
    padding: 10px;
    margin-top: 5px;
    border: 1px solid #ccc;

    textarea {
      width: 100%;
      height: 100%;
      outline: none;
      border: none;
      font-size: 20px;
    }
  }
}
</style>

3.4、编写Show组件

在 show.vue 中通过 run 方法解析 用户输入的代码。在 run 方法中,通过正则匹配到 template、script、style。将 script 包装成函数的返回值并和 template 合并成一个对象,用 this.$options._base.extend 构造器动态生成组件,再用 $mount() 将组件挂载到DOM上。最后添加 style 即可。(this.$options._base === Vue)

<template>
  <div class="show">
    <h2 class="show-title">运行结果</h2>
    <div class="show-box" ref="display"></div>
  </div>
</template>

<script>
export default {
  props: {
    code: {
      type: String,
      code: "",
    },
  },
  methods: {
    getSource(type) {
      const reg = new RegExp(`<${type}[^>]*>`);
      let code = this.code;
      let matches = code.match(reg);
      if (matches) {
        return code.slice(
          code.indexOf(matches[0]) + matches[0].length,
          code.lastIndexOf(`</${type}`)
        );
      }
      return "";
    },
    run() {
      // 运行代码
      // 1.获取 js html css逻辑
      const template = this.getSource("template");
      const script = this.getSource("script").replace(
        /export default/,
        "return"
      );
      const style = this.getSource("style");

      if (!template) {
        return alert("代码中应包含 template");
      }
      // 2.组合成组件
      let component = new Function(script)();
      component.template = template;

      // 3.构造组件构造器
      let instance = new (this.$options._base.extend(component))();
      // 先清空页面
      this.$refs.display.innerHTML = "";
      this.$refs.display.appendChild(instance.$mount().$el);

      // 4.处理样式
      if (style) {
        let styleElement = document.createElement("style");
        styleElement.type = "text/css";
        styleElement.innerHTML = style;
        document.getElementsByTagName("head")[0].appendChild(styleElement);
      }
    },
  },
};
</script>

Show.vue 添加样式

<style lang="stylus">
.show {
  box-sizing: border-box;
  border: 1px solid #ccc;
  padding: 10px !important;

  .show-title {
    line-height: 35px;
    border-bottom: 1px solid #ccc;
  }

  .show-box {
    position: relative;
  }
}
</style>
  • 完结.