把第三方的 UI 组件封装为 Vue 组件,以Frappe Gantt为例 想要在Vue中使用甘特图组件,参考vue-echart的实现
转化的步骤
- 在on Mount的hook中,初始化,并且将回调转换为事件emit
- 实现 v-model 双向绑定
- 初始化,然后在js库回调中emit事件
- 将js库所有更改传递到vue组件(在回调事件emit函数中修改传入的props)
- 外部vue组件的改变传递到js库(用watch来实现,当vue组件状态改变时候,同步修改js库的ui)
- 最后处理对于插槽(选项列表)内部内容的所有变更。(用updated 生命周期hook)
什么是Web Components
https://developer.mozilla.org/zh-CN/docs/Web/API/Web_components/Using_custom_elements 需要将封装为一个web component
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Custom Element Demo</title>
</head>
<body>
<my-custom-element color="blue" size="20px">Click me!</my-custom-element>
<script>
class MyCustomElement extends HTMLElement {
static observedAttributes = ["color", "size"];
constructor() {
super();
this.attachShadow({ mode: "open" }); // 使用 Shadow DOM
this.shadowRoot.innerHTML = `
<style>
:host {
display: inline-block;
padding: 10px;
border: 2px solid black;
cursor: pointer;
}
</style>
<slot></slot>
`;
}
connectedCallback() {
console.log("自定义元素添加至页面。");
this.updateStyle(); // 初始化样式
this.addEventListener("click", this.handleClick);
}
disconnectedCallback() {
console.log("自定义元素从页面中移除。");
this.removeEventListener("click", this.handleClick);
}
attributeChangedCallback(name, oldValue, newValue) {
console.log(`属性 ${name} 已变更:从 "${oldValue}" 变为 "${newValue}"`);
this.updateStyle(); // 属性变化时更新样式
}
updateStyle() {
const color = this.getAttribute("color") || "black";
const size = this.getAttribute("size") || "16px";
this.style.color = color;
this.style.fontSize = size;
}
handleClick() {
alert("自定义元素被点击!");
}
}
customElements.define("my-custom-element", MyCustomElement);
// 动态更改属性
setTimeout(() => {
const customElement = document.querySelector("my-custom-element");
customElement.setAttribute("color", "red");
customElement.setAttribute("size", "24px");
}, 2000);
</script>
</body>
</html>
遇到的问题
- 第三方组件gantt在内部拖拽时更新了tasks,导致watcheffet循环响应了。循环,导致无限更新
1. 外部的tasks更改后,要同步更新js组件库的ui
2. 而可以通过js组件库的ui更改tasks。也要同步修改到外部的vue的tasks中
分为两种情况:
- 如果js组件只读数据,就可以用
export default defineComponent({
props: {
tasks: Array as PropType<Tasks>,
options: Object,
},
emits: ["date-change", "update:modelValue"],
setup(props, context) {
const root = shallowRef<GanttElement>();
const gantt = shallowRef<Gantt>();
function init() {
if (!root.value) {
console.error("root is ", root.value);
return;
}
const instance = (gantt.value = new Gantt(root.value, props.tasks, {
...props.options,
on_date_change: (task) => {
// 在这里可以获取最新的任务数据
context.emit("date-change", task);
// 修改props.tasks,通过事件返回
// context.emit("update:modelValue",)
},
}));
if (instance) {
console.log("Gantt chart initialized successfully.");
} else {
console.error("Failed to initialize Gantt chart.");
}
}
onMounted(() => {
init();
});
watchEffect(()=> {
if (props.tasks && gantt.value){
console.log("tasks update")
gantt.value.refresh(props.tasks);
}
})
return {
root,
};
},
render() {
const attrs = {} as any;
attrs.ref = "root";
return h(TAG_NAME, attrs);
},
});
- 如果js组件会修改数据,就不能像上面那样,因为修改点有两个(vue的响应式,js内部组件改变数据)
-
- 明确数据单向流动:
- Vue 外部更新
tasks
,传递给 JS 组件库,触发 UI 更新。 - JS 组件库通过事件回调通知 Vue 修改
tasks
,由 Vue 统一管理数据。
- Vue 外部更新
- 明确数据单向流动:
- 避免直接操作响应式数据:
- 通过本地
ref
副本中转tasks
,避免直接修改props.tasks
。
- 通过本地
-
watch的新、旧值输出一样
https://stackoverflow.com/questions/62729380/vue-watch-outputs-same-oldvalue-and-newvalue