工作暂时结束了
吐槽
前段时间一直在做学院集市的新前端, 今天终于暂时结束了, 感谢另一位前端的同学写新ui.
怎么说呢, 最开始只是想着锻炼自己的能力吧, 加上早晚要写, 干脆就自己写了. 一些不喜欢的人和不喜欢的发言, 差点就想删库开摆了. 如果不是后面突然有说可能按勤工俭学来发钱搞不好真的就不写了.
一些收获
使用jsx封装组件
这是我第一次使用jsx来封装组件, 也是封装组件最多的一次.
一般方式封装组件:
<!-- MessageBox.vue -->
<template>
<div>{{msg}}</div>
<button @click="close">X</button>
</template>
<script setup>
defineProps({
msg:{
type:string,
required:true
},
close:{
type:Function,
required:true
}
})
</script>
使用组件:
<template>
<div>
<!-- 其它代码 -->
<!-- 实际可能需要在其它地方触发 -->
<button @click="isShow = true">模拟触发弹窗</button>
<message-box v-if="isShow" :msg="msg" />
</div>
</template>
<script setup>
import { ref } from 'vue'
import MessageBox from 'MessageBox.vue'
const msg = ref('hello')
const isShow = ref(false)
const close = ()=>{
isShow.value = !isShow.value
}
</script>
这是一般方法封装组件, 如果要调用的话会发现要加很多行. 如果经常调用就可能会很痛苦.
jsx封装:
import { createApp } from "vue";
const messageBox = {
props: {
msg: {
type: String,
required: true
},
close:{
type:Function,
required:true
}
},
setup(props) {
return () => (
<div id="msgbox-root">
<p>{props.msg}</p>
<button>X</button>
</div>
)
}
}
function showMsg(msg) {
const div = document.createElement("div");
document.body.appendChild(div);
const app = createApp(messageBox,{
msg:msg,
close:()=>{
app.unmount(div)
document.body.removeChild(div)
}
})
app.mount(div)
}
export { showMsg };
使用组件:
<template>
<div>
<!-- 其它代码 -->
<!-- 实际可能需要在其它地方触发 -->
<button @click="showMsg('hello')">模拟触发弹窗</button>
</div>
</template>
<script setup>
import { showMsg } from '@/components/MessageBox.jsx'
</script>
使用对比
<template>
<div>
<!-- 其它代码 -->
<!-- 实际可能需要在其它地方触发 -->
<button @click="isShow = true">模拟触发弹窗</button>
<message-box v-if="isShow" :msg="msg" />
</div>
</template>
<script setup>
import { ref } from 'vue'
import MessageBox from 'MessageBox.vue'
const msg = ref('hello')
const isShow = ref(false)
const close = ()=>{
isShow.value = !isShow.value
}
</script>
<template>
<div>
<!-- 其它代码 -->
<!-- 实际可能需要在其它地方触发 -->
<button @click="showMsg('hello')">模拟触发弹窗</button>
</div>
</template>
<script setup>
import { showMsg } from '@/components/MessageBox.jsx'
</script>
从代码长度来看也能看出来用jsx封装组件的便利性, 需要使用的时候只需要导入一下函数, 再调用一下就可以了.
我觉得那个up说的挺好的, 使用jsx的话实现可能比一般方式要复杂一点, 但是调用的时候更加简单方便. 封装是为了让使用简单, 而不是为了实现简单.
markdown代码复制功能
当时使用的是marked库, 第一次用不太熟悉. 在开发者工具里发现code都在pre标签里, 第一个想法是在每个pre里都加一个绝对定位的button放右上角, 但是有点不想这么做捏, 让我遍历整个markdown内容, 再往pre里加button, 感觉会有点损耗性能, 于是得到了第二个想法:使用::before或者::after再给它们一个背景图片充当按钮.
这样也算是得到了一个按钮吧, 但是又有一个问题, 伪元素是不能绑定事件的, 如果要绑定只能绑定整个pre. 不过如果我点一下代码就复制我干嘛弄个按钮?最后选择在css里将pre的点击事件设置为none, 再将::before伪元素的点击事件设置为auto
:deep(pre){
position: relative;
pointer-events: none;
z-index: 0;
}
:deep(pre::before) {
background-image: url('/PhCopy.webp');
position: absolute;
z-index: 1;
top: 3px;
right: 3px;
pointer-events: auto;
}
这样的话给pre绑定点击事件只有before伪元素可以触发了. 不过我也没有给pre绑定事件, 实在不想遍历整个markdown内容再绑定事件捏.
const clickHandler = async (event) => {
/**
* 在css里已经去除了pre标签的点击, 只保留了pre::before的点击
*/
if (event.target.tagName === 'PRE') {
const code = event.target.innerText;
await navigator.clipboard.writeText(code);
showMsg('代码已复制');
} else if (event.target.tagName === 'IMG') {
//拿到图片的src
const src = event.target.src;
// 如果class名为user-avatar, 直接展示
if (event.target.className === 'user-avatar') {
showImg(src);
return;
}
const uploadImg = strHandler('postImg', src);
showImg(uploadImg);
}
};
这里将点击事件绑定到了根元素, 这种方式好像叫做事件托管?托管给它们的父元素, 点到其中某个元素执行相关的操作. 另一个图片的是放大图片的功能, imageShower同样是使用jsx封装的, 用来展示图片.
封装组件最好趁早
别让等待成为遗憾, 等代码成屎山之后我觉得不会有人想碰它了. 可惜我封装的太晚, 勉强封装了几个组件就不想动了. 帖子详情页面感觉一坨, 很难想象我当时的精神状态, 命名也是一坨, 还好写了一点注释. 最后把基卡片、帖子卡片和详情卡片封装好了, 评论卡片实在有点痛苦, 还是改日吧, 反正能跑捏, 不管是代码还是我.
封装的好处也是比较明显的. 改集市的等级只需要在基卡片里改就可以了, 不用列表改一遍, 详情页面再改一遍.
貌似封装组件还能稍微地提升性能, 只要不过度封装.
第一次写这么大的东西, 写了大概五六千行代码吧, 希望不会有bug.