[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"sidebar-data":3,"post-scss入门到精通":351,"comments-POST-5e06c564-c8f8-47c3-97e6-2fa678d5ce7b-1":364},{"categories":4,"tags":93,"postCount":277,"tagCount":278,"hotPosts":279},[5,13,20,27,33,40,47,51,58,62,66,72,78,83,87],{"id":6,"name":7,"slug":8,"description":9,"sortOrder":10,"createdAt":11,"updatedAt":11,"postCount":12},"e8d0bd45-d10c-46d3-8afb-0c072df7f8a7","技术","tech","技术文章",0,"2026-06-27T04:18:37.371Z",1,{"id":14,"name":15,"slug":16,"description":15,"sortOrder":12,"createdAt":17,"updatedAt":18,"postCount":19},"15ac46ad-edf7-4435-9a64-ff78117d58c5","Vue3 生态","vue3-生态","2022-05-21T08:05:39.000Z","2023-02-08T02:49:14.000Z",6,{"id":21,"name":22,"slug":23,"description":22,"sortOrder":24,"createdAt":25,"updatedAt":18,"postCount":26},"11d4d397-685c-4180-a7b3-9b0e3a1e411e","Css","css",2,"2022-05-23T07:19:37.000Z",9,{"id":28,"name":29,"slug":29,"description":29,"sortOrder":30,"createdAt":31,"updatedAt":18,"postCount":32},"d10456a5-e649-4741-a38f-f07f266ce5f2","开发环境",3,"2022-05-24T01:52:41.000Z",13,{"id":34,"name":35,"slug":36,"description":35,"sortOrder":37,"createdAt":38,"updatedAt":18,"postCount":39},"5ed5cc62-43ea-49a2-b0b2-38bc7aae52a0","Vue3","vue3",4,"2022-05-24T01:55:05.000Z",8,{"id":41,"name":42,"slug":43,"description":42,"sortOrder":44,"createdAt":45,"updatedAt":18,"postCount":46},"da130ba9-d4f4-49f3-aa0f-149078097ef0","JavaScript","javascript",5,"2022-05-24T02:22:57.000Z",18,{"id":48,"name":49,"slug":49,"description":49,"sortOrder":19,"createdAt":50,"updatedAt":18,"postCount":12},"d8cbe380-54b3-4a61-a12d-5438c2918574","限时优惠","2022-05-25T07:18:03.000Z",{"id":52,"name":53,"slug":54,"description":53,"sortOrder":55,"createdAt":56,"updatedAt":18,"postCount":57},"e0f3b8d8-cfe7-41fb-802b-a79699d95968","JavaScript插件","javascript插件",7,"2022-06-01T14:08:31.000Z",16,{"id":59,"name":60,"slug":60,"description":60,"sortOrder":39,"createdAt":61,"updatedAt":18,"postCount":19},"4ea3d8af-9cc3-49bb-a9cd-34dbcdc3bd85","构建工具","2022-06-02T07:28:13.000Z",{"id":63,"name":64,"slug":64,"description":64,"sortOrder":26,"createdAt":65,"updatedAt":18,"postCount":39},"9ed9827c-9cbb-42da-80e4-d04c7fdba886","开发工具","2022-06-21T03:35:05.000Z",{"id":67,"name":68,"slug":69,"description":68,"sortOrder":70,"createdAt":71,"updatedAt":18,"postCount":44},"6b9179c3-17b2-43ff-a431-a03d6eb32d89","Vue2 生态","vue2-生态",10,"2022-07-16T13:14:29.000Z",{"id":73,"name":74,"slug":75,"description":74,"sortOrder":76,"createdAt":77,"updatedAt":18,"postCount":44},"73a5f62c-3c47-45b9-9ae2-f29953ae8dc0","Node","node",11,"2022-07-16T13:15:39.000Z",{"id":79,"name":80,"slug":80,"description":80,"sortOrder":81,"createdAt":82,"updatedAt":18,"postCount":24},"2b696c16-48ef-403b-a88b-6e57cfc79596","开发问题",12,"2022-07-16T14:06:54.000Z",{"id":84,"name":85,"slug":85,"description":85,"sortOrder":32,"createdAt":86,"updatedAt":18,"postCount":12},"c0f0561e-a47a-4ecd-8caa-cc1df2315d57","算法","2022-07-16T14:22:34.000Z",{"id":88,"name":89,"slug":90,"description":89,"sortOrder":91,"createdAt":92,"updatedAt":18,"postCount":30},"a629c1f7-29f1-439e-be3c-29670b17ba20","Vue2","vue2",15,"2022-07-16T14:41:51.000Z",[94,100,105,108,113,118,122,126,131,136,141,146,149,154,158,162,167,172,177,182,186,190,194,196,200,205,210,213,216,219,222,225,229,232,234,236,239,243,246,250,254,257,260,262,265,267,271,274],{"id":95,"name":96,"slug":97,"createdAt":98,"updatedAt":99},"076bd8b9-293e-45cb-9dc3-e162007ca474","Axios","axios","2022-06-05T07:41:56.000Z","2025-12-30T07:26:21.000Z",{"id":101,"name":102,"slug":103,"createdAt":104,"updatedAt":18},"2aa7f6d0-1fac-4ed1-b9bb-f3afc813f42c","Axure","axure","2022-06-21T03:35:15.000Z",{"id":106,"name":22,"slug":23,"createdAt":107,"updatedAt":18},"b084ddd8-09be-4e57-98f0-cf4e376aecd7","2022-05-21T09:59:55.000Z",{"id":109,"name":110,"slug":111,"createdAt":112,"updatedAt":18},"78a62bff-ff77-4878-8c25-3e6aae18c668","Docker","docker","2022-07-16T14:34:37.000Z",{"id":114,"name":115,"slug":116,"createdAt":117,"updatedAt":18},"2de16806-ef3f-4e54-a259-d1e1e182468c","Git","git","2022-07-16T14:25:15.000Z",{"id":119,"name":120,"slug":121,"createdAt":107,"updatedAt":18},"994cc226-578b-4a72-a57e-a47a63d2793e","JavaScript生态","javascript生态",{"id":123,"name":124,"slug":125,"createdAt":107,"updatedAt":18},"5086e93c-23b9-43d3-9643-cc87f0e9ee94","JenKins","jenkins",{"id":127,"name":128,"slug":129,"createdAt":130,"updatedAt":18},"b73007a8-bb5c-42a8-9fd9-163033a5b45d","Linux","linux","2022-07-16T14:40:17.000Z",{"id":132,"name":133,"slug":134,"createdAt":135,"updatedAt":18},"0b658b92-dd6b-4db3-a398-9f6d69950a02","Markdown","markdown","2022-07-16T14:39:25.000Z",{"id":137,"name":138,"slug":139,"createdAt":140,"updatedAt":18},"ab034d3a-6e5b-4db5-a2dc-faf4ccbb63f5","Nest","nest","2022-07-16T13:15:49.000Z",{"id":142,"name":143,"slug":144,"createdAt":145,"updatedAt":18},"52c41978-da06-4962-9636-45bbaeedda80","Nginx","nginx","2022-05-21T09:59:56.000Z",{"id":147,"name":148,"slug":148,"createdAt":145,"updatedAt":18},"0f1cc678-40e4-44b1-b2cf-a6fd8a1c867a","npm",{"id":150,"name":151,"slug":152,"createdAt":153,"updatedAt":18},"a4370d78-70e1-4073-a8f6-3dc5d81fd8fd","Nuxt","nuxt","2022-06-01T13:07:07.000Z",{"id":155,"name":156,"slug":157,"createdAt":107,"updatedAt":18},"d232e01f-048e-4151-8a0a-fff9561f946f","Pinia","pinia",{"id":159,"name":160,"slug":161,"createdAt":145,"updatedAt":18},"14e9ab02-b0bb-4c85-8604-fe6f1f0f33cd","Pnpm","pnpm",{"id":163,"name":164,"slug":165,"createdAt":166,"updatedAt":166},"399d1d38-cc0d-43ce-8baf-c769447a2ebd","React生态","react生态","2023-02-21T02:03:09.000Z",{"id":168,"name":169,"slug":170,"createdAt":171,"updatedAt":18},"c95bbe84-bdd0-410a-86a9-e87958c55f4f","Redis","redis","2022-10-05T05:14:14.000Z",{"id":173,"name":174,"slug":175,"createdAt":176,"updatedAt":18},"6d05f9df-e116-450f-af57-85ed710c4870","Swiper","swiper","2022-06-01T14:08:46.000Z",{"id":178,"name":179,"slug":180,"createdAt":181,"updatedAt":18},"66f3aeb0-84ef-45f6-a43a-944eefc9895a","Vite","vite","2022-06-02T07:28:24.000Z",{"id":183,"name":184,"slug":185,"createdAt":107,"updatedAt":18},"bf5b94d3-090b-4098-a03c-4bc69781fb2d","Vue","vue",{"id":187,"name":188,"slug":189,"createdAt":145,"updatedAt":18},"2f7fb1be-b9c5-4606-b54f-e9f66f2653b2","Vue-Router","vue-router",{"id":191,"name":192,"slug":193,"createdAt":107,"updatedAt":18},"2fef3b91-1c1c-4ae8-b2c1-0e04b4f9b3a2","Vue2生态","vue2生态",{"id":195,"name":35,"slug":36,"createdAt":107,"updatedAt":18},"62b94c93-724f-488d-8fc5-0449971d9204",{"id":197,"name":198,"slug":199,"createdAt":107,"updatedAt":18},"20bff9cd-7848-4c16-8775-42cf12b44b30","Vue3生态","vue3生态",{"id":201,"name":202,"slug":203,"createdAt":204,"updatedAt":18},"c807b2c6-cb12-4409-a1f1-6bea9f330a6b","Vuex","vuex","2022-07-16T13:14:59.000Z",{"id":206,"name":207,"slug":208,"createdAt":209,"updatedAt":18},"5782dff5-2ea2-4427-9696-d4363a7fd5bc","Webpack","webpack","2022-07-16T14:33:41.000Z",{"id":211,"name":212,"slug":212,"createdAt":107,"updatedAt":18},"d0aa41f4-68f8-48d4-a4ed-3a503ea90451","下载",{"id":214,"name":215,"slug":215,"createdAt":107,"updatedAt":18},"a046060c-39ef-474a-8c85-2546aca0e2e5","代码片段",{"id":217,"name":218,"slug":218,"createdAt":107,"updatedAt":18},"fee73435-b2be-4b55-85b1-d133ea96aea4","伪元素",{"id":220,"name":221,"slug":221,"createdAt":107,"updatedAt":18},"436bd369-8c57-4869-8827-e88e50e5e0ab","伪类",{"id":223,"name":224,"slug":224,"createdAt":107,"updatedAt":18},"4c6be544-8a00-4445-92a3-e3dcbaf6142e","动画",{"id":226,"name":227,"slug":227,"createdAt":228,"updatedAt":18},"9321a12e-ea72-49a9-a32d-5566149f812f","图片压缩","2022-08-02T00:37:47.000Z",{"id":230,"name":231,"slug":231,"createdAt":145,"updatedAt":18},"512b16fb-576a-4397-a7c5-dd20e6a8f9ca","布局",{"id":233,"name":64,"slug":64,"createdAt":107,"updatedAt":18},"f32faa96-f2ec-45c6-9a17-2c76062edcb0",{"id":235,"name":29,"slug":29,"createdAt":107,"updatedAt":18},"3c46ed3f-6d6b-4f91-bcb3-af5112860bf5",{"id":237,"name":238,"slug":238,"createdAt":107,"updatedAt":18},"dbfc086a-73a6-4560-814d-593acb61cf98","性能优化",{"id":240,"name":241,"slug":241,"createdAt":242,"updatedAt":18},"1831cd06-0d6b-48f7-94fa-324782fe23cb","拖拽","2022-07-28T12:39:13.000Z",{"id":244,"name":245,"slug":245,"createdAt":145,"updatedAt":18},"9a74300d-06f7-46d0-80d9-8fe67ec0539b","数组",{"id":247,"name":248,"slug":248,"createdAt":249,"updatedAt":18},"19ac8998-7e0a-459b-9702-bb1adca70e8c","文本复制","2022-07-17T01:54:45.000Z",{"id":251,"name":252,"slug":252,"createdAt":253,"updatedAt":18},"5ff33473-71a4-4e02-8c82-f9ea369a768f","时间","2022-07-17T01:51:12.000Z",{"id":255,"name":256,"slug":256,"createdAt":107,"updatedAt":18},"aa47ca4d-d3f6-4cac-b495-2c67c9592c36","最新优惠",{"id":258,"name":259,"slug":259,"createdAt":145,"updatedAt":18},"f6766d54-54fc-405e-932d-b7d550559125","服务器",{"id":261,"name":60,"slug":60,"createdAt":107,"updatedAt":18},"d856559a-03ff-40b4-980d-3f272b998c3c",{"id":263,"name":264,"slug":264,"createdAt":107,"updatedAt":18},"692d5d68-b188-4e5c-aca8-65d0229399a1","渐变",{"id":266,"name":85,"slug":85,"createdAt":107,"updatedAt":18},"38e1fd6b-d7c6-4d62-bf70-7bacc175bea9",{"id":268,"name":269,"slug":269,"createdAt":270,"updatedAt":18},"be7b10bc-49eb-4a03-bea7-ceb915d500fe","规范","2022-07-16T14:41:06.000Z",{"id":272,"name":273,"slug":273,"createdAt":145,"updatedAt":18},"b42e2916-ad62-4b8a-a863-cd8c19a829de","面试",{"id":275,"name":276,"slug":276,"createdAt":145,"updatedAt":18},"7069add9-b636-44f1-9cd4-ea3a6d2b85d3","面试题",104,48,[280,297,309,322,335],{"id":281,"title":282,"slug":282,"content":283,"excerpt":282,"coverImage":284,"status":285,"isPinned":286,"pinnedAt":287,"viewCount":288,"publishedAt":289,"createdAt":289,"updatedAt":290,"category":291,"author":292,"tags":295},"a1bd1f49-6f6d-4fea-9789-b5636e19a6b3","uni-app瀑布流","## 实现思路\n> 获取父组件的列表数组，watch监听数组长度变化，截取后面新的数据，创建两个左右数组，比较左右dom的长度，哪个短，就push一条数据进去，源数组删除一条数据。利用img的load（加载成功）和error方法（加载失败），触发数组的push，实现瀑布流\n\n## 代码实现\n\n```language\n\u003Ctemplate>\n    \u003Cview class=\\\"waterfall\\\">\n        \u003Cview class=\\\"waterfall_left\\\">\n            \u003Cview class=\\\"waterfall_list\\\" v-for=\\\"(item, index) in leftList\\\" :key=\\\"index\\\">\n                \u003CSearch\n                    :name=\\\"item.name\\\"\n                    :image=\\\"item.src\\\"\n                    :label=\\\"item.label\\\"\n                    :item=\\\"item\\\"\n                    @considerPush=\\\"considerPush\\\"\n                >\n                \u003C\u002FSearch>\n            \u003C\u002Fview>\n        \u003C\u002Fview>\n        \u003Cview class=\\\"waterfall_right\\\">\n            \u003Cview class=\\\"waterfall_list\\\" v-for=\\\"(item, index) in rightList\\\" :key=\\\"index\\\">\n                \u003CSearch\n                    :name=\\\"item.name\\\"\n                    :image=\\\"item.src\\\"\n                    :label=\\\"item.label\\\"\n                    :item=\\\"item\\\"\n                    @considerPush=\\\"considerPush\\\"\n                >\n                \u003C\u002FSearch>\n            \u003C\u002Fview>\n        \u003C\u002Fview>\n    \u003C\u002Fview>\n\u003C\u002Ftemplate>\n\n\u003Cscript>\nimport Search from '@\u002Fcomponents\u002Fsearch\u002Fsearch.vue';\nexport default {\n    components: { Search },\n    props: {\n        list: {\n            type: Array,\n            default: () => []\n        }\n    },\n    data() {\n        return {\n            \u002F\u002F 左侧列表\n            leftList: [],\n            \u002F\u002F 右侧列表\n            rightList: [],\n            \u002F\u002F 组件数据备份\n            newList: [],\n            \u002F\u002F默认请求数,主要为了正常排序\n            interceptNumber: 10\n        };\n    },\n    created() {\n        this.touchOff(); \u002F\u002F 触发排列\n    },\n    mounted() {},\n    watch: {\n        list(newValue, oldValue) {\n            this.interceptNumber = newValue.length - oldValue.length;\n            this.touchOff();\n        }\n    },\n    computed: {},\n    methods: {\n        \u002F\u002F 触发重新排列\n        touchOff() {\n            this.newList = [...this.list.slice(-this.interceptNumber)];\n            if (this.newList.length !== 0) {\n                this.leftList.push(this.newList.shift()); \u002F\u002F触发排列\n            }\n        },\n        \u002F\u002F 计算排列\n        considerPush() {\n            this.$nextTick(() => {\n                if (this.newList.length == 0) return; \u002F\u002F没有数据了\n                let leftH = 0;\n                let rightH = 0; \u002F\u002F左右高度\n                let query = uni.createSelectorQuery().in(this);\n                query.selectAll('.waterfall_left').boundingClientRect();\n                query.selectAll('.waterfall_right').boundingClientRect();\n                query.exec(res => {\n                    leftH = res[0].length != 0 ? res[0][0].height : 0; \u002F\u002F防止查询不到做个处理\n                    rightH = res[1].length != 0 ? res[1][0].height : 0;\n                    if (leftH == rightH || leftH \u003C rightH) {\n                        \u002F\u002F 相等 || 左边小\n                        this.leftList.push(this.newList.shift());\n                    } else {\n                        \u002F\u002F 右边小\n                        this.rightList.push(this.newList.shift());\n                    }\n\n                    \u002F\u002F console.log('左右高度：', leftH, rightH, leftH == rightH || leftH \u003C rightH);\n                });\n            });\n        }\n    }\n};\n\u003C\u002Fscript>\n\n\u003Cstyle scoped lang=\\\"scss\\\">\n.waterfall {\n    display: flex;\n    align-items: flex-start;\n    justify-content: flex-start;\n    .waterfall_left {\n        flex: 1;\n    }\n\n    .waterfall_right {\n        flex: 1;\n    }\n}\n\u003C\u002Fstyle>\n\n```\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F7d4fa8d1d2775177b882a4656e3a5ed5.png","PUBLISHED",false,null,1819,"2022-11-22T07:54:41.000Z","2026-06-27T11:37:20.140Z",{"id":41,"name":42,"slug":43},{"id":293,"name":294,"avatar":287},"f9d0f2de-c700-4f90-b535-afd3dbe78128","Admin",[296],{"id":119,"name":120,"slug":121},{"id":298,"title":299,"slug":299,"content":300,"excerpt":299,"coverImage":301,"status":285,"isPinned":286,"pinnedAt":287,"viewCount":302,"publishedAt":303,"createdAt":303,"updatedAt":304,"category":305,"author":306,"tags":307},"b2c46bf6-d971-4cce-b21b-052dbea8e8a2","v-html使用img点击实现放大效果","## 代码实现\n```js\n\u002F**\n * JS获取html代码中所有的图片地址\n * @param htmlstr\n * @returns arr 数组\n *\u002F\n\nexport function getimgsrc(htmlstr) {\n    let reg = \u002F\u003Cimg.+?src=('|\\\")?([^'\\\"]+)('|\\\")?(?:\\s+|>)\u002Fg;\n    let arr = [];\n    let tem = 0;\n    \u002F\u002Feslint-disable-next-line\n    while ((tem = reg.exec(htmlstr))) {\n        arr.push(tem[2]); \u002F\u002F eslint-disable-line\n    }\n\n    return arr;\n}\n\n```\n\n\n\n```vue\n\u003Ctemplate>\n    \u003Cdiv class=\\\"image-expansion\\\" :class=\\\"classArr\\\">\n        \u003Cdiv @click.stop=\\\"hanldeImage($event)\\\" v-html=\\\"formatHtmlData\\\">\u003C\u002Fdiv>\n\n        \u003Cel-image-viewer\n            v-if=\\\"imgPreviewUrl\\\"\n            :initial-index=\\\"subscript\\\"\n            :src=\\\"imgPreviewUrl\\\"\n            :on-close=\\\"closeViewer\\\"\n            :url-list=\\\"imgList\\\"\n        >\u003C\u002Fel-image-viewer>\n    \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cscript>\nimport { getimgsrc } from '..\u002F..\u002Futils\u002Fgetimgsrc';\nimport ElImageViewer from 'element-ui\u002Fpackages\u002Fimage\u002Fsrc\u002Fimage-viewer';\nexport default {\n    components: {\n        ElImageViewer\n    },\n    props: {\n        htmlData: {\n            type: String,\n            default: () => {\n                return '';\n            }\n        },\n        classArr: {\n            type: Array,\n            default: () => {\n                return ['min'];\n            }\n        },\n\n        isArticle: {\n            type: Boolean,\n            default: () => {\n                return false;\n            }\n        }\n    },\n    data() {\n        return {\n            imgList: [],\n            formatHtmlData: '',\n            imgPreviewUrl: '',\n            subscript: 0\n        };\n    },\n    computed: {},\n\n    watch: {\n        \u002F\u002F监听数据，防止数据不更新\n        htmlData: {\n            handler(newName, oldName) {\n                \u002F\u002F判断是否为文章\n                if (this.isArticle) {\n                    newName ? (this.formatHtmlData = newName.replace(\u002F(\u003C([^>]+)>)\u002Fgi, '').replace(\u002F[\\\r\\\n]\u002Fg, '')) : '';\n                } else {\n                    \u002F\u002F剔除strong和p标签\n                    newName ? (this.formatHtmlData = newName.replace(\u002F(\u003C\\\u002F?strong.*?>)|(\u003C\\\u002F?p.*?>)\u002Fg, '')) : '';\n\n                    \u002F\u002F获取html全部图片，push成图片数组\n                    this.imgList = Object.values(getimgsrc(this.formatHtmlData));\n                    \u002F\u002F获取图片下标\n                    let subscript = this.imgList.indexOf(this.imgPreviewUrl);\n                    this.subscript = subscript > -1 ? subscript : 0;\n                }\n            },\n\n            immediate: true\n        }\n    },\n\n    mounted() {},\n\n    methods: {\n        \u002F\u002F监听点击事件\n        hanldeImage(event) {\n            if (event.target.nodeName === 'IMG' || event.target.nodeName === 'img') {\n                \u002F\u002F获取点击的图片url,decodeURIComponent转码一下，防禁url转码\n                this.imgPreviewUrl = decodeURIComponent(event.target.currentSrc);\n\n                \u002F\u002F获取图片下标\n                let subscript = this.imgList.indexOf(this.imgPreviewUrl);\n                this.subscript = subscript > -1 ? subscript : 0;\n\n                \u002F\u002F禁止遮罩层后面的内容滚动\n                document.documentElement.style.overflowY = 'hidden';\n            } else {\n                this.$emit('goDetail');\n            }\n        },\n\n        \u002F\u002F关闭弹框\n        closeViewer() {\n            this.imgPreviewUrl = '';\n            \u002F\u002F恢复遮罩层后面的内容滚动\n            document.documentElement.style.overflowY = 'auto';\n        }\n    }\n};\n\u003C\u002Fscript>\n\n\u003Cstyle lang=\\\"scss\\\" scoped>\n.image-expansion {\n}\n\n.min {\n    \u002Fdeep\u002F img {\n        cursor: pointer;\n        height: 28px;\n        padding: 0 10px 3px;\n    }\n}\n\n.max {\n    \u002Fdeep\u002F img {\n        cursor: pointer;\n    }\n}\n\n.class1 {\n    \u002Fdeep\u002F div {\n        font-size: 15px;\n        font-family: Microsoft YaHei;\n        font-weight: 400;\n        color: #888888;\n        line-height: 30px;\n    }\n}\n\u003C\u002Fstyle>\n\n\n```\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002Fc77c3fb113d1ab2f67e7afba1ca33b95.png",1572,"2023-01-10T07:22:29.000Z","2026-06-27T11:07:40.499Z",{"id":67,"name":68,"slug":69},{"id":293,"name":294,"avatar":287},[308],{"id":226,"name":227,"slug":227},{"id":310,"title":311,"slug":312,"content":313,"excerpt":311,"coverImage":314,"status":285,"isPinned":286,"pinnedAt":287,"viewCount":315,"publishedAt":316,"createdAt":316,"updatedAt":317,"category":318,"author":319,"tags":320},"9f33b49b-0785-4404-9a4e-d85937e7fc92","在 vue3 中优雅的使用 jsx\u002Ftsx","在-vue3-中优雅的使用-jsx-tsx","## 安装插件（@vitejs\u002Fplugin-vue-jsx）\n```shell\npnpm add @vitejs\u002Fplugin-vue-jsx -D\n```\n\n## 配置vite.config.ts\n```ts\nimport vueJsx from \\\"@vitejs\u002Fplugin-vue-jsx\\\";\n\nexport default defineConfig({\n  plugins: [\n    vueJsx(),\n  ]\n})\n```\n\n## 插值\n```language\n\u002F\u002F vue3模板语法\n\u003Cspan>{{ a + b }}\u003C\u002Fspan>\n\n\u002F\u002F jsx\u002Ftsx\n\u003Cspan>{ a + b }\u003C\u002Fspan>\n```\n\n\n## class与style 绑定\n\n```ts\n\u002F\u002F 模板字符串\n\u003Cdiv className={`header ${ isBg ? 'headerBg' : '' }`}>header\u003C\u002Fdiv>\n\u002F\u002F数组\n\u003Cdiv class={ [ 'header', isBg && 'headerBg' ] } >header\u003C\u002Fdiv>\n```\n\n```ts\nconst color = 'red'\nconst element = \u003Csapn style={{ color, fontSize: '16px' }}>style\u003C\u002Fsapn>\n```\n\n## 条件渲染\n\n```ts\n   setup() {\n       const isShow = false\n       const element = () => {\n           if (isShow) {\n               return \u003Cspan>我是if\u003C\u002Fspan>\n           } else {\n               return \u003Cspan>我是else\u003C\u002Fspan>\n           }\n       }\n       return () => (\n           \u003Cdiv>\n               \u003Cspan v-show={isShow}>我是v-show\u003C\u002Fspan>\n               {\n                   element()\n               }\n               {\n                   isShow ? \u003Cp>我是三目1\u003C\u002Fp> : \u003Cp>我是三目2\u003C\u002Fp>\n               }\n           \u003Cdiv>\n       )\n   }\n```\n\n## 列表渲染\n```language\nsetup() {\n   const listData = [\n       {name: 'Tom', age: 18},\n       {name: 'Jim', age: 20},\n       {name: 'Lucy', age: 16}\n   ]\n   return () => (\n       \u003Cdiv>\n           \u003Cdiv class={'box'}>\n               \u003Cspan>姓名\u003C\u002Fspan>\n               \u003Cspan>年龄\u003C\u002Fspan>\n           \u003C\u002Fdiv>\n           {\n               prop.listData.map(item => {\n                   return \u003Cdiv class={'box'}>\n                       \u003Cspan>{item.name}\u003C\u002Fspan>\n                       \u003Cspan>{item.age}\u003C\u002Fspan>\n                   \u003C\u002Fdiv>\n               })\n           }\n       \u003C\u002Fdiv>\n   )\n}\n\n```\n\n## 事件处理\n\n```language\nsetup() {\n    const clickBox = val => {\n        console.log(val)\n    }\n    return () => (\n        \u003Cdiv class={'box1'} onClick={() => clickBox('box1')}>\n            \u003Cspan>我是box1\u003C\u002Fspan>\n            \u003Cdiv class={'box2'} onClick={() => clickBox('box2')}>\n                \u003Cspan>我是box2\u003C\u002Fspan>\n                \u003Cdiv class={'box3'} onClick={withModifiers(() => clickBox('box3'), ['stop'])}>我是box3\u003C\u002Fdiv>\n            \u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n    )\n}\n```\n\n## v-model\n\n```ts\n\u002F\u002F 正常写法\n\u003Cinput v-model=\\\"value\\\" \u002F> \u002F\u002F vue\n\u003Cinput v-model={value} \u002F> \u002F\u002F jsx\n\n\u002F\u002F 指定绑定值写法\n\u003Cinput v-model:modelValue=\\\"value\\\" \u002F> \u002F\u002F vue\n\u003Cinput v-model={[value,'modelValue']} \u002F> \u002F\u002F jsx\n\n\u002F\u002F 修饰符写法\n\u003Cinput v-model:modelValue.trim=\\\"value\\\" \u002F> \u002F\u002F vue\n\u003Cinput v-model={[value,'modelValue',['trim']]} \u002F> \u002F\u002F jsx\n```\n\n## slot插槽\n\n### 定义插槽\n```ts\nimport { renderSlot } from \\\"vue\\\"\nexport default defineComponent({\n    \u002F\u002F 从ctx中解构出来 slots\n    setup(props, { slots }) {\n        return () => (\n            \u003Cdiv>\n                { renderSlot(slots, 'default') }\n                { slots.title?.() }\n            \u003C\u002Fdiv>\n        )\n    }\n})\n\n```\n\n### 使用插槽\n\n```ts\nimport Vslot from '.\u002FslotTem'\nexport default defineComponent({\n    setup() {\n        return () => (\n            \u003Cdiv class={'box'}>\n                \u003CVslot v-slots={{\n                    title: () => {\n                        return \u003Cp>我是title插槽\u003C\u002Fp>\n                    },\n                    default: () => {\n                        return \u003Cp>我是default插槽\u003C\u002Fp>\n                    }\n                }} \u002F>\n            \u003C\u002Fdiv>\n        )\n    }\n})\n\n```\n\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F3e4cb0d3df611ee3f57b8ed503e1015e.png",1392,"2023-04-19T09:23:27.000Z","2026-06-27T11:08:34.303Z",{"id":34,"name":35,"slug":36},{"id":293,"name":294,"avatar":287},[321],{"id":195,"name":35,"slug":36},{"id":323,"title":324,"slug":325,"content":326,"excerpt":324,"coverImage":327,"status":285,"isPinned":286,"pinnedAt":287,"viewCount":328,"publishedAt":329,"createdAt":329,"updatedAt":330,"category":331,"author":332,"tags":333},"1dcdd755-0e46-45a3-8998-9a213fd3fcd5","Vue3 导入导出Excel","vue3-导入导出excel","## 安装\n```shell\npnpm add -S XLSX\n```\n\n\n## 方法封装\n```js\nimport * as XLSX from 'xlsx';\n\n\u002F\u002F参数说明\n\u002F\u002Fconfiguration: {\n\u002F\u002F  data: [], \u002F\u002F 导出的数据\n\u002F\u002F  head: {}, \u002F\u002F 导出的数据对应的表头\n\u002F\u002F  name: '', \u002F\u002F 导出的文件名\n\u002F\u002F  label: '', \u002F\u002F 导出的表单名\n\u002F\u002F  widthArr: [], \u002F\u002F 导出的表单列宽\n\u002F\u002F}\n\n\u002F\u002F 导出excel\nexport const ExportXlsx = (configuration) => {\n  const { data, head, name, label, widthArr } = configuration;\n\n  const list = data.map((item) => {\n    const obj = {};\n    for (const k in item) {\n      if (head[k]) {\n        obj[head[k]] = item[k];\n      }\n    }\n    return obj;\n  });\n\n  \u002F\u002F 创建工作表\n  const xLSXData = XLSX.utils.json_to_sheet(list);\n  \u002F\u002F 创建工作簿\n  const wb = XLSX.utils.book_new();\n  \u002F\u002F 将工作表放入工作簿中\n  XLSX.utils.book_append_sheet(wb, xLSXData, label);\n  xLSXData['!cols'] = [];\n  \u002F\u002F 设置列宽\n  widthArr.forEach((item) => {\n    xLSXData['!cols'].push({ wpx: item });\n  });\n\n  \u002F\u002F 生成文件并下载\n  XLSX.writeFile(wb, `${name}.xlsx`);\n};\n\n\u002F\u002F 导入excel\nexport const ImportXlsx = (e) => {\n  const file = e.target.files[0];\n  const reader = new FileReader();\n  reader.readAsArrayBuffer(file);\n  reader.onload = (e) => {\n    const data = e.target.result;\n    const workbook = XLSX.read(data, { type: 'binary', cellDates: true });\n    const wsname = workbook.SheetNames[0];\n    const outdata = XLSX.utils.sheet_to_json(workbook.Sheets[wsname]);\n    console.log(outdata);\n  };\n};\n\n```\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F80231a326d3846b895a7d46c99e738ce.png",1336,"2023-03-14T06:18:59.000Z","2026-06-27T11:37:23.273Z",{"id":41,"name":42,"slug":43},{"id":293,"name":294,"avatar":287},[334],{"id":119,"name":120,"slug":121},{"id":336,"title":337,"slug":338,"content":339,"excerpt":337,"coverImage":340,"status":285,"isPinned":286,"pinnedAt":287,"viewCount":341,"publishedAt":342,"createdAt":342,"updatedAt":343,"category":344,"author":348,"tags":349},"80920598-a452-4357-bf53-842b200560e8","React18入门到精通教程","react18入门到精通教程","\n## JSX实现列表渲染\n> 使用`map()`方法遍历数组，必须添加`key`属性提高性能\n```jsx\nconst songs = [\n  { id: 1, name: \\\"helo1\\\" },\n  { id: 2, name: \\\"helo2\\\" },\n  { id: 3, name: \\\"helo3\\\" },\n];\n\nfunction App() {\n  return (\n    \u003Cdiv>\n      \u003Cul>\n        {songs.map((song) => (\n          \u002F\u002F 关键：添加唯一key标识符（避免使用索引）\n          \u003Cli key={song.id}>  \n            {song.id}-{song.name}\n          \u003C\u002Fli>\n        ))}\n      \u003C\u002Ful>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n**最佳实践：**\n1. 使用`\u003Cul>`包裹列表项\n2. `key`应使用稳定唯一标识（如ID），避免数组索引\n3. 空列表处理：`{songs.length > 0 && ...}` 或 `{songs.map(...) || \u003CEmptyView\u002F>}`\n\n## JSX实现条件渲染\n### 简单逻辑\n```jsx\n\u002F\u002F 三元表达式\n{isLoggedIn ? \u003CDashboard \u002F> : \u003CLoginForm \u002F>}\n\n\u002F\u002F 逻辑短路\n{hasNotification && \u003CNotificationBadge count={5} \u002F>}\n\n\u002F\u002F 空值处理\n{userProfile?.avatar || \u003CDefaultAvatar \u002F>}\n```\n\n### 复杂逻辑\n```jsx\nconst renderContent = (type) => {\n  switch(type) {\n    case 'success': \n      return \u003CSuccessAlert \u002F>;\n    case 'error':\n      return \u003CErrorAlert \u002F>;\n    default:\n      return \u003CInfoAlert \u002F>;\n  }\n}\n\nfunction App() {\n  return (\n    \u003Cdiv className=\\\"container\\\">\n      {renderContent(status)}\n      \n      {\u002F* 另一种模式：立即执行函数 *\u002F}\n      {(() => {\n        if (isLoading) return \u003CSpinner \u002F>;\n        if (isEmpty) return \u003CEmptyState \u002F>;\n        return \u003CDataTable \u002F>;\n      })()}\n    \u003C\u002Fdiv>\n  )\n}\n```\n\n## JSX样式处理\n### 行内样式\n```jsx\n\u002F\u002F 直接对象\n\u003Cdiv style={{ \n  color: 'white', \n  backgroundColor: 'teal',\n  padding: '1rem'\n}}>\n\n\u002F\u002F 样式对象复用\nconst alertStyle = {\n  padding: '15px',\n  borderRadius: '4px',\n  margin: '10px 0'\n};\n\nfunction Alert({ type }) {\n  return (\n    \u003Cdiv style={{\n      ...alertStyle,  \u002F\u002F 扩展运算符合并样式\n      background: type === 'error' ? '#f8d7da' : '#d4edda'\n    }}>\n      {message}\n    \u003C\u002Fdiv>\n  )\n}\n```\n\n### 类名控制（推荐）\n```css\n\u002F* styles.module.css *\u002F\n.card {\n  border: 1px solid #ddd;\n  border-radius: 8px;\n  padding: 20px;\n  box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n}\n\n.highlight {\n  background-color: #ffffe0;\n}\n```\n\n```jsx\nimport styles from '.\u002Fstyles.module.css';\n\nfunction ProductCard({ featured }) {\n  return (\n    \u003Cdiv className={`${styles.card} ${featured ? styles.highlight : ''}`}>\n      {\u002F* 内容 *\u002F}\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n## React 18新特性\n### 并发模式（Concurrent Mode）\n```jsx\nimport { startTransition } from 'react';\n\n\u002F\u002F 非紧急状态更新\nfunction handleSearch(query) {\n  startTransition(() => {\n    setSearchQuery(query); \u002F\u002F 可中断的渲染\n  });\n}\n```\n\n### 自动批处理（Automatic Batching）\n```jsx\n\u002F\u002F React 17及之前：两次渲染\n\u002F\u002F React 18：自动批处理，一次渲染\nfunction handleClick() {\n  setCount(c => c + 1);\n  setFlag(f => !f);\n}\n```\n\n## Redux状态管理（现代写法）\n### 安装依赖\n```bash\nnpm install @reduxjs\u002Ftoolkit react-redux\n```\n\n### 创建Store\n```js\n\u002F\u002F store.js\nimport { configureStore, createSlice } from '@reduxjs\u002Ftoolkit';\n\nconst counterSlice = createSlice({\n  name: 'counter',\n  initialState: { value: 0 },\n  reducers: {\n    increment: state => { state.value += 1 },\n    decrement: state => { state.value -= 1 },\n    incrementByAmount: (state, action) => {\n      state.value += action.payload\n    }\n  }\n});\n\n\u002F\u002F 异步操作示例\nexport const fetchUserData = () => async (dispatch) => {\n  const response = await fetch('\u002Fapi\u002Fuser');\n  dispatch(setUser(await response.json()));\n};\n\nexport const store = configureStore({\n  reducer: {\n    counter: counterSlice.reducer,\n    \u002F\u002F 其他reducer...\n  }\n});\n\nexport const { increment, decrement } = counterSlice.actions;\n```\n\n### 组件集成\n```jsx\n\u002F\u002F index.js\nimport { Provider } from 'react-redux';\nimport { store } from '.\u002Fstore';\n\nroot.render(\n  \u003CProvider store={store}>\n    \u003CApp \u002F>\n  \u003C\u002FProvider>\n);\n\n\u002F\u002F Counter.js\nimport { useSelector, useDispatch } from 'react-redux';\nimport { increment, decrement } from '.\u002Fstore';\n\nfunction Counter() {\n  const count = useSelector(state => state.counter.value);\n  const dispatch = useDispatch();\n\n  return (\n    \u003Cdiv>\n      \u003Cbutton onClick={() => dispatch(decrement())}>-\u003C\u002Fbutton>\n      \u003Cspan>{count}\u003C\u002Fspan>\n      \u003Cbutton onClick={() => dispatch(increment())}>+\u003C\u002Fbutton>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n## 最佳实践总结\n1. **组件设计**：遵循单一职责原则，拆分智能组件（容器组件）和展示组件\n2. **状态管理**：\n   - 局部状态用`useState`\u002F`useReducer`\n   - 全局共享状态用Redux\n   - 避免过度使用状态提升\n3. **性能优化**：\n   - 使用`React.memo`记忆组件\n   - 使用`useCallback`\u002F`useMemo`避免不必要的重渲染\n   - 虚拟化长列表（react-window）\n4. **Hooks规范**：\n   - 避免在循环\u002F条件中使用Hook\n   - 自定义Hook以`use`前缀命名\n5. **TypeScript集成**：\n   ```tsx\n   interface UserCardProps {\n     name: string;\n     age: number;\n     onSelect: (id: string) => void;\n   }\n   \n   const UserCard: React.FC\u003CUserCardProps> = ({ name, age }) => (\n     \u003Cdiv>{name} ({age})\u003C\u002Fdiv>\n   )\n   ```\n\n下面为您完善教程，增加React路由和生命周期相关内容：\n\n## React路由管理（React Router v6）\n\n### 安装与基础配置\n```bash\nnpm install react-router-dom@6\n```\n\n### 路由基础结构\n```jsx\n\u002F\u002F index.js\nimport { BrowserRouter } from 'react-router-dom';\n\nconst root = ReactDOM.createRoot(document.getElementById('root'));\nroot.render(\n  \u003CBrowserRouter>\n    \u003CApp \u002F>\n  \u003C\u002FBrowserRouter>\n);\n\n\u002F\u002F App.js\nimport { Routes, Route, Link } from 'react-router-dom';\n\nfunction App() {\n  return (\n    \u003Cdiv>\n      \u003Cnav>\n        \u003CLink to=\\\"\u002F\\\">首页\u003C\u002FLink>\n        \u003CLink to=\\\"\u002Fabout\\\">关于\u003C\u002FLink>\n        \u003CLink to=\\\"\u002Fusers\\\">用户列表\u003C\u002FLink>\n      \u003C\u002Fnav>\n      \n      \u003CRoutes>\n        \u003CRoute path=\\\"\u002F\\\" element={\u003CHome \u002F>} \u002F>\n        \u003CRoute path=\\\"\u002Fabout\\\" element={\u003CAbout \u002F>} \u002F>\n        \u003CRoute path=\\\"\u002Fusers\\\" element={\u003CUserList \u002F>} \u002F>\n        \u003CRoute path=\\\"\u002Fusers\u002F:id\\\" element={\u003CUserDetail \u002F>} \u002F>\n        \u003CRoute path=\\\"*\\\" element={\u003CNotFound \u002F>} \u002F>\n      \u003C\u002FRoutes>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n### 嵌套路由\n```jsx\n\u002F\u002F Dashboard.js\nimport { Outlet } from 'react-router-dom';\n\nfunction Dashboard() {\n  return (\n    \u003Cdiv>\n      \u003Ch2>仪表盘\u003C\u002Fh2>\n      \u003COutlet \u002F> {\u002F* 子路由渲染位置 *\u002F}\n    \u003C\u002Fdiv>\n  );\n}\n\n\u002F\u002F 路由配置\n\u003CRoutes>\n  \u003CRoute path=\\\"\u002Fdashboard\\\" element={\u003CDashboard \u002F>}>\n    \u003CRoute index element={\u003CDashboardHome \u002F>} \u002F>\n    \u003CRoute path=\\\"settings\\\" element={\u003CDashboardSettings \u002F>} \u002F>\n    \u003CRoute path=\\\"analytics\\\" element={\u003CDashboardAnalytics \u002F>} \u002F>\n  \u003C\u002FRoute>\n\u003C\u002FRoutes>\n```\n\n### 编程式导航\n```jsx\nimport { useNavigate, useParams, useLocation } from 'react-router-dom';\n\nfunction UserCard({ user }) {\n  const navigate = useNavigate();\n  \n  return (\n    \u003Cdiv onClick={() => navigate(`\u002Fusers\u002F${user.id}`)}>\n      {user.name}\n    \u003C\u002Fdiv>\n  );\n}\n\nfunction UserDetail() {\n  const { id } = useParams(); \u002F\u002F 获取URL参数\n  const location = useLocation(); \u002F\u002F 获取位置对象\n  \n  return (\n    \u003Cdiv>\n      \u003Ch2>用户ID: {id}\u003C\u002Fh2>\n      \u003Cp>当前路径: {location.pathname}\u003C\u002Fp>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n### 路由守卫（认证保护）\n```jsx\nimport { Navigate } from 'react-router-dom';\n\nfunction ProtectedRoute({ children }) {\n  const { isAuthenticated } = useAuth();\n  \n  if (!isAuthenticated) {\n    return \u003CNavigate to=\\\"\u002Flogin\\\" replace \u002F>;\n  }\n  \n  return children;\n}\n\n\u002F\u002F 使用\n\u003CRoute \n  path=\\\"\u002Fdashboard\\\" \n  element={\n    \u003CProtectedRoute>\n      \u003CDashboard \u002F>\n    \u003C\u002FProtectedRoute>\n  } \n\u002F>\n```\n\n## React生命周期\n\n### 类组件生命周期方法\n\n```jsx\nclass LifecycleDemo extends React.Component {\n  \u002F\u002F 1. 初始化阶段\n  constructor(props) {\n    super(props);\n    this.state = { count: 0 };\n    console.log('Constructor');\n  }\n\n  \u002F\u002F 2. 挂载阶段\n  componentDidMount() {\n    console.log('Component did mount');\n    \u002F\u002F 适合进行API调用、事件订阅\n    this.timer = setInterval(() => {\n      this.setState(prev => ({ count: prev.count + 1 }));\n    }, 1000);\n  }\n\n  \u002F\u002F 3. 更新阶段\n  shouldComponentUpdate(nextProps, nextState) {\n    console.log('Should component update?');\n    return nextState.count !== this.state.count;\n  }\n\n  componentDidUpdate(prevProps, prevState) {\n    console.log('Component did update');\n  }\n\n  \u002F\u002F 4. 卸载阶段\n  componentWillUnmount() {\n    console.log('Component will unmount');\n    \u002F\u002F 清理操作\n    clearInterval(this.timer);\n  }\n\n  render() {\n    console.log('Render');\n    return \u003Cdiv>Count: {this.state.count}\u003C\u002Fdiv>;\n  }\n}\n```\n\n### 函数组件生命周期（Hooks实现）\n\n```jsx\nimport { useState, useEffect } from 'react';\n\nfunction FunctionLifecycle() {\n  const [count, setCount] = useState(0);\n  const [data, setData] = useState(null);\n\n  \u002F\u002F 相当于componentDidMount + componentDidUpdate\n  useEffect(() => {\n    console.log('每次渲染后执行');\n  });\n\n  \u002F\u002F 相当于componentDidMount\n  useEffect(() => {\n    console.log('组件挂载后执行');\n    \n    \u002F\u002F 数据获取\n    fetch('\u002Fapi\u002Fdata')\n      .then(res => res.json())\n      .then(setData);\n    \n    \u002F\u002F 相当于componentWillUnmount\n    return () => {\n      console.log('组件卸载前执行');\n    };\n  }, []); \u002F\u002F 空依赖数组\n\n  \u002F\u002F 依赖变化时执行\n  useEffect(() => {\n    console.log('count变化时执行:', count);\n    \n    document.title = `Count: ${count}`;\n    \n    return () => {\n      console.log('清理count效果');\n    };\n  }, [count]); \u002F\u002F count依赖\n\n  return (\n    \u003Cdiv>\n      \u003Cp>Count: {count}\u003C\u002Fp>\n      \u003Cbutton onClick={() => setCount(c => c + 1)}>增加\u003C\u002Fbutton>\n      {data && \u003Cpre>{JSON.stringify(data, null, 2)}\u003C\u002Fpre>}\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n### 生命周期阶段对比\n\n| 阶段 | 类组件方法 | 函数组件Hook |\n|------|------------|--------------|\n| **挂载** | constructor | useState初始化 |\n|       | render      | 函数体执行    |\n|       | componentDidMount | useEffect(() => {}, []) |\n| **更新** | shouldComponentUpdate | React.memo, useMemo |\n|       | render      | 函数体执行    |\n|       | componentDidUpdate | useEffect(() => {}) |\n| **卸载** | componentWillUnmount | useEffect返回函数 |\n| **错误处理** | componentDidCatch | 暂无直接等效，需错误边界组件 |\n\n### 现代React开发建议\n\n1. **优先使用函数组件+Hooks**：\n   - 90%的场景可替代类组件\n   - 更简洁的代码结构\n   - 更好的逻辑复用\n\n2. **关键生命周期替代**：\n   - `componentDidMount` → `useEffect(() => {}, [])`\n   - `componentDidUpdate` → `useEffect(() => {})` 或带依赖的 `useEffect`\n   - `componentWillUnmount` → `useEffect(() => { return cleanup }, [])`\n   - `shouldComponentUpdate` → `React.memo` 或 `useMemo`\n\n3. **数据获取最佳实践**：\n```jsx\nuseEffect(() => {\n  let isMounted = true;\n  \n  const fetchData = async () => {\n    try {\n      const result = await api.getData();\n      if (isMounted) setData(result);\n    } catch (error) {\n      if (isMounted) setError(error);\n    }\n  };\n  \n  fetchData();\n  \n  return () => {\n    isMounted = false; \u002F\u002F 避免组件卸载后设置状态\n  };\n}, []);\n```\n\n## 路由与生命周期整合示例\n\n```jsx\nfunction UserProfile() {\n  const { id } = useParams();\n  const [user, setUser] = useState(null);\n  const [loading, setLoading] = useState(true);\n\n  useEffect(() => {\n    let isActive = true;\n    \n    const fetchUser = async () => {\n      try {\n        setLoading(true);\n        const data = await fetchUserById(id);\n        if (isActive) {\n          setUser(data);\n          setLoading(false);\n        }\n      } catch (error) {\n        if (isActive) {\n          setError(error.message);\n          setLoading(false);\n        }\n      }\n    };\n    \n    fetchUser();\n    \n    return () => {\n      isActive = false; \u002F\u002F 清理效果\n    };\n  }, [id]); \u002F\u002F id变化时重新获取\n\n  if (loading) return \u003CSpinner \u002F>;\n  \n  return (\n    \u003Cdiv>\n      \u003Ch2>{user.name}\u003C\u002Fh2>\n      \u003Cp>Email: {user.email}\u003C\u002Fp>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n## 完整项目结构建议\n\n```\nsrc\u002F\n├── components\u002F      # 通用UI组件\n├── pages\u002F           # 页面组件\n├── layouts\u002F         # 布局组件\n├── hooks\u002F           # 自定义Hooks\n├── store\u002F           # Redux状态\n│   ├── slices\u002F\n│   └── store.js\n├── services\u002F        # API服务\n├── routers\u002F         # 路由配置\n├── utils\u002F           # 工具函数\n├── assets\u002F          # 静态资源\n└── App.js           # 主应用组件\n```\n\n这些新增内容涵盖了React路由的现代用法（v6版本）以及React生命周期的详细解释，包括类组件和函数组件的实现方式对比。同时还提供了路由与生命周期整合的实际示例，帮助开发者理解如何在真实项目中应用这些概念。","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F46e205aa1bd33d1bb7201019fc2fdf43.png",1148,"2023-02-21T02:01:52.000Z","2026-06-27T11:00:41.557Z",{"id":345,"name":346,"slug":347},"f1701085-b8c1-413a-8750-58e7a0a33832","React","react",{"id":293,"name":294,"avatar":287},[350],{"id":163,"name":164,"slug":165},{"id":352,"title":353,"slug":354,"content":355,"excerpt":353,"coverImage":356,"status":285,"isPinned":286,"pinnedAt":287,"viewCount":357,"publishedAt":358,"createdAt":358,"updatedAt":359,"category":360,"author":361,"tags":362},"5e06c564-c8f8-47c3-97e6-2fa678d5ce7b","Scss入门到精通","scss入门到精通","\n\n## 什么是 CSS 预处理器？\n\n> CSS 预处理器用一种专门的编程语言，进行 Web 页面样式设计，然后再编译成正常的 CSS 文件，以供项目使用。CSS 预处理器为 CSS 增加一些编程的特性，无需考虑浏览器的兼容性问题”，例如你可以在 CSS 中使用变量、简单的逻辑程序、函数等等在编程语言中的一些基本特性，可以让你的 CSS 更加简洁、适应性更强**、**可读性更佳，更易于代码的维护等诸多好处。\n\n\n\n## [Sass]声明变量\n\n#### Sass 的变量包括三个部分：\n\n- 声明变量的符号“$”\n\n- 变量名称\n\n- 赋予变量的值\n\n来看一个简单的示例，假设你的按钮颜色可以给其声明几个变量：\n\n```scss\n$brand-primary : darken(#428bca, 6.5%) !default; \u002F\u002F #337ab7\n$btn-primary-color : #fff !default;\n$btn-primary-bg : $brand-primary !default;\n$btn-primary-border : darken($btn-primary-bg, 5%) !default;\n```\n\n>  如果值后面加上!default则表示默认值。\n\n\n\n## [Sass]普通变量与默认变量\n\n#### 普通变量\n\n>  定义之后可以在全局范围内使用。\n\n```scss\n$fontSize: 12px;\nbody{\n    font-size:$fontSize;\n}\n```\n\n#### 编译后的css代码：\n\n```scss\nbody{\n    font-size:12px;\n}\n```\n\n\n\n#### 默认变量\n\n> sass 的默认变量仅需要在值后面加上 !default 即可。\n\n```scss\n$baseLineHeight:1.5 !default;\nbody{\n    line-height: $baseLineHeight; \n}\n```\n\n#### 编译后的css代码：\n\n```scss\nbody{\n    line-height:1.5;\n}\n```\n\n>  sass 的默认变量一般是用来设置默认值，然后根据需求来覆盖的，覆盖的方式也很简单，只需要在默认变量之前重新声明下变量即可。\n\n```scss\n$baseLineHeight: 2;\n$baseLineHeight: 1.5 !default;\nbody{\n    line-height: $baseLineHeight; \n}\n```\n\n#### 编译后的css代码：\n\n```scss\nbody{\n    line-height:2;\n}\n```\n\n>  可以看出现在编译后的 line-height 为 2，而不是我们默认的 1.5。默认变量的价值在进行组件化开发的时候会非常有用。\n\n\n\n## [Sass]变量的调用\n\n> 比如在定义了变量\n\n```scss\n$brand-primary : darken(#428bca, 6.5%) !default; \u002F\u002F #337ab7\n$btn-primary-color: #fff !default;\n$btn-primary-bg : $brand-primary !default;\n$btn-primary-border : darken($btn-primary-bg, 5%) !default;\n```\n\n> 在按钮 button 中调用，可以按下面的方式调用\n\n```scss\n.btn-primary {\n   background-color: $btn-primary-bg;\n   color: $btn-primary-color;\n   border: 1px solid $btn-primary-border;\n}\n```\n\n> 编译出来的CSS:\n\n```scss\n.btn-primary {\n  background-color: #337ab7;\n  color: #fff;\n  border: 1px solid #2e6da4;\n}\n```\n\n [Sass]局部变量和全局变量\n\nSass 中变量的作用域在过去几年已经发生了一些改变。直到最近，规则集和其他范围内声明变量的作用域才默认为本地。如果已经存在同名的全局变量，从 3.4 版本开始，Sass 已经可以正确处理作用域的概念，并通过创建一个新的局部变量来代替。\n\n\n\n#### 全局变量与局部变量\n\n```scss\n$color: orange !default;\u002F\u002F定义全局变量(在选择器、函数、混合宏...的外面定义的变量为全局变量)\n.block {\n  color: $color;\u002F\u002F调用全局变量\n}\nem {\n  $color: red;\u002F\u002F定义局部变量\n  a {\n    color: $color;\u002F\u002F调用局部变量\n  }\n}\nspan {\n  color: $color;\u002F\u002F调用全局变量\n}\n```\n\n> css 的结果：\n\n```scss\n.block {\n  color: orange;\n}\nem a {\n  color: red;\n}\nspan {\n  color: orange;\n}\n```\n\n\n\n上面的示例演示可以得知，在元素内部定义的变量不会影响其他元素。如此可以简单的理解成，**全局变量**就是定义在元素外面的变量，如下代码：\n\n```\n$color:orange !default;\n```\n\n$color 就是一个全局变量，而定义在元素内部的变量，比如 $color:red; 是一个**局部变量**。\n\n除此之外，Sass 现在还提供一个 !global 参数。!global 和 !default 对于定义变量都是很有帮助的。我们之后将会详细介绍这两个参数的使用以及其功能。\n\n**全局变量的影子**\n\n当在局部范围（选择器内、函数内、混合宏内...）声明一个已经存在于全局范围内的变量时，局部变量就成为了**全局变量的影子**。基本上，**局部变量只会在局部范围内覆盖全局变量**。\n\n上面例子中的 em 选择器内的变量 $color 就是一个全局变量的影子。\n\n```scss\n\u002F\u002FSCSS\n$color: orange !default;\u002F\u002F定义全局变量\n.block {\n  color: $color;\u002F\u002F调用全局变量\n}\nem {\n  $color: red;\u002F\u002F定义局部变量（全局变量 $color 的影子）\n  a {\n    color: $color;\u002F\u002F调用局部变量\n  }\n}\n```\n\n**什么时候声明变量？**\n\n我的建议，创建变量只适用于感觉确有必要的情况下。不要为了某些骇客行为而声明新变量，这丝毫没有作用。只有满足所有下述标准时方可创建新变量：\n\n1. 该值至少重复出现了两次；\n2. 该值至少可能会被更新一次；\n3. 该值所有的表现都与变量有关（非巧合）。\n\n基本上，没有理由声明一个永远不需要更新或者只在单一地方使用变量。\n\n\n\n## [Sass]嵌套-选择器嵌套\n\nSass 中还提供了选择器嵌套功能，但这也并不意味着你在 Sass 中的嵌套是无节制的，因为你嵌套的层级越深，编译出来的 CSS 代码的选择器层级将越深，这往往是大家不愿意看到的一点。这个特性现在正被众多开发者滥用。\n\n选择器嵌套为样式表的作者提供了一个通过局部选择器相互嵌套实现全局选择的方法，Sass 的嵌套分为三种：\n\n- 选择器嵌套\n- 属性嵌套\n- 伪类嵌套\n\n1、选择器嵌套\n\n假设我们有一段这样的结构：\n\n```scss\n\u003Cheader>\n\u003Cnav>\n    \u003Ca href=“##”>Home\u003C\u002Fa>\n    \u003Ca href=“##”>About\u003C\u002Fa>\n    \u003Ca href=“##”>Blog\u003C\u002Fa>\n\u003C\u002Fnav>\n\u003Cheader>\n```\n\n想选中 header 中的 a 标签，在写 CSS 会这样写：\n\n```scss\nnav a {\n  color:red;\n}\n\nheader nav a {\n  color:green;\n}\n```\n\n那么在 Sass 中，就可以使用选择器的嵌套来实现：\n\n```scss\nnav {\n  a {\n    color: red;\n\n    header & {\n      color:green;\n    }\n  }  \n}\n```\n\n \n\n### 任务\n\n在编辑器第 2 行和第 5 行输入正确的选择器，使其编译出来的CSS如下：\n\n```scss\nnav a {\n   color: red;\n}\n\nheader nav a {\n   color: green;\n}\n```\n\n## [Sass]嵌套-属性嵌套\n\nSass 中还提供属性嵌套，CSS 有一些属性前缀相同，只是后缀不一样，比如：border-top\u002Fborder-right，与这个类似的还有 margin、padding、font 等属性。假设你的样式中用到了：\n\n```scss\n.box {\n    border-top: 1px solid red;\n    border-bottom: 1px solid green;\n}\n```\n\n在 Sass 中我们可以这样写：\n\n```scss\n.box {\n  border: {\n   top: 1px solid red;\n   bottom: 1px solid green;\n  }\n}\n```\n\n## [Sass]嵌套-伪类嵌套\n\n其实伪类嵌套和属性嵌套非常类似，只不过他需要借助`&`符号一起配合使用。我们就拿经典的“clearfix”为例吧：\n\n```scss\n.clearfix{\n&:before,\n&:after {\n    content:\\\"\\\";\n    display: table;\n  }\n&:after {\n    clear:both;\n    overflow: hidden;\n  }\n}\n```\n\n编译出来的 CSS：\n\n```\nclearfix:before, .clearfix:after {\n  content: \\\"\\\";\n  display: table;\n}\n.clearfix:after {\n  clear: both;\n  overflow: hidden;\n}\n```\n\n**避免选择器嵌套：**\n\n- 选择器嵌套最大的问题是将使最终的代码难以阅读。开发者需要花费巨大精力计算不同缩进级别下的选择器具体的表现效果。\n- 选择器越具体则声明语句越冗长，而且对最近选择器的引用(&)也越频繁。在某些时候，出现混淆选择器路径和探索下一级选择器的错误率很高，这非常不值得。\n\n为了防止此类情况，我们应该尽可能避免选择器嵌套。然而，显然只有少数情况适应这一措施。\n\n\n\n## [Sass]混合宏-声明混合宏\n\n如果你的整个网站中有几处小样式类似，比如颜色，字体等，在 Sass 可以使用变量来统一处理，那么这种选择还是不错的。但当你的样式变得越来越复杂，需要重复使用大段的样式时，使用变量就无法达到我们目了。这个时候 Sass 中的混合宏就会变得非常有意义。在这一节中，主要向大家介绍 Sass 的混合宏。\n\n1、声明混合宏\n\n**不带参数混合宏**：\n\n在 Sass 中，使用“@mixin”来声明一个混合宏。如：\n\n```scss\n@mixin border-radius{\n    -webkit-border-radius: 5px;\n    border-radius: 5px;\n}\n```\n\n其中 @mixin 是用来声明混合宏的关键词，有点类似 CSS 中的 @media、@font-face 一样。border-radius 是混合宏的名称。大括号里面是复用的样式代码。\n\n**带参数混合宏**：\n\n除了声明一个不带参数的混合宏之外，还可以在定义混合宏时带有参数，如：\n\n```scss\n@mixin border-radius($radius:5px){\n    -webkit-border-radius: $radius;\n    border-radius: $radius;\n}\n```\n\n**复杂的混合宏：**\n\n上面是一个简单的定义混合宏的方法，当然， Sass 中的混合宏还提供更为复杂的，你可以在大括号里面写上带有逻辑关系，帮助更好的做你想做的事情,如：\n\n```scss\n@mixin box-shadow($shadow...) {\n  @if length($shadow) >= 1 {\n    @include prefixer(box-shadow, $shadow);\n  } @else{\n    $shadow:0 0 4px rgba(0,0,0,.3);\n    @include prefixer(box-shadow, $shadow);\n  }\n}\n```\n\n这个 box-shadow 的混合宏，带有多个参数，这个时候可以使用“ … ”来替代。简单的解释一下，当 $shadow 的参数数量值大于或等于“ 1 ”时，表示有多个阴影值，反之调用默认的参数值“ 0 0 4px rgba(0,0,0,.3) ”。\n\n注：复杂的混合宏中的逻辑关系（@if...@else）后面小节会有讲解。\n\n\n\n## [Sass]混合宏-调用混合宏\n\n在 Sass 中通过 @mixin 关键词声明了一个混合宏，那么在实际调用中，其匹配了一个关键词“@include”来调用声明好的混合宏。例如在你的样式中定义了一个圆角的混合宏“border-radius”:\n\n```scss\n@mixin border-radius{\n    -webkit-border-radius: 3px;\n    border-radius: 3px;\n}\n```\n\n在一个按钮中要调用定义好的混合宏“border-radius”，可以这样使用：\n\n```scss\nbutton {\n    @include border-radius;\n}\n```\n\n这个时候编译出来的 CSS:\n\n```scss\nbutton {\n  -webkit-border-radius: 3px;\n  border-radius: 3px;\n}\n```\n\n\n\n## [Sass]混合宏的参数--传一个不带值的参数\n\nSass 的混合宏有一个强大的功能，可以传参，那么在 Sass 中传参主要有以下几种情形：\n\nA) 传一个不带值的参数\n\n在混合宏中，可以传一个不带任何值的参数，比如：\n\n```scss\n@mixin border-radius($radius){\n  -webkit-border-radius: $radius;\n  border-radius: $radius;\n}\n```\n\n在混合宏“border-radius”中定义了一个不带任何值的参数“$radius”。\n\n在调用的时候可以给这个混合宏传一个参数值：\n\n```scss\n.box {\n  @include border-radius(3px);\n}\n```\n\n这里表示给混合宏传递了一个“border-radius”的值为“3px”。\n\n编译出来的 CSS:\n\n```scss\n.box {\n  -webkit-border-radius: 3px;\n  border-radius: 3px;\n}\n```\n\n## [Sass]混合宏的参数--传一个带值的参数\n\n在 Sass 的混合宏中，还可以给混合宏的参数传一个默认值，例如：\n\n```scss\n@mixin border-radius($radius:3px){\n  -webkit-border-radius: $radius;\n  border-radius: $radius;\n}\n```\n\n在混合宏“border-radius”传了一个参数“$radius”，而且给这个参数赋予了一个默认值“3px”。\n\n在调用类似这样的混合宏时，会多有一个机会，假设你的页面中的圆角很多地方都是“3px”的圆角，那么这个时候只需要调用默认的混合宏“border-radius”:\n\n```scss\n.btn {\n  @include border-radius;\n}\n```\n\n编译出来的 CSS:\n\n```scss\n.btn {\n  -webkit-border-radius: 3px;\n  border-radius: 3px;\n}\n```\n\n但有的时候，页面中有些元素的圆角值不一样，那么可以随机给混合宏传值，如：\n\n```scss\n.box {\n  @include border-radius(50%);\n}\n```\n\n编译出来的 CSS:\n\n```scss\n.box {\n  -webkit-border-radius: 50%;\n  border-radius: 50%;\n}\n```\n\n \n\n## [Sass]混合宏的参数--传多个参数\n\nSass 混合宏除了能传一个参数之外，还可以传多个参数，如：\n\n```scss\n@mixin center($width,$height){\n  width: $width;\n  height: $height;\n  position: absolute;\n  top: 50%;\n  left: 50%;\n  margin-top: -($height) \u002F 2;\n  margin-left: -($width) \u002F 2;\n}\n```\n\n在混合宏“center”就传了多个参数。在实际调用和其调用其他混合宏是一样的：\n\n```scss\n.box-center {\n  @include center(500px,300px);\n}\n```\n\n编译出来 CSS:\n\n```scss\n.box-center {\n  width: 500px;\n  height: 300px;\n  position: absolute;\n  top: 50%;\n  left: 50%;\n  margin-top: -150px;\n  margin-left: -250px;\n}\n```\n\n 有一个特别的参数“**…**”。当混合宏传的参数过多之时，可以使用参数来替代，如：\n\n```scss\n@mixin box-shadow($shadows...){\n  @if length($shadows) >= 1 {\n    -webkit-box-shadow: $shadows;\n    box-shadow: $shadows;\n  } @else {\n    $shadows: 0 0 2px rgba(#000,.25);\n    -webkit-box-shadow: $shadow;\n    box-shadow: $shadow;\n  }\n}\n```\n\n在实际调用中：\n\n```scss\n.box {\n  @include box-shadow(0 0 1px rgba(#000,.5),0 0 2px rgba(#000,.2));\n}\n```\n\n编译出来的CSS:\n\n```scss\n.box {\n  -webkit-box-shadow: 0 0 1px rgba(0, 0, 0, 0.5), 0 0 2px rgba(0, 0, 0, 0.2);\n  box-shadow: 0 0 1px rgba(0, 0, 0, 0.5), 0 0 2px rgba(0, 0, 0, 0.2);\n}\n```\n\n## [Sass]混合宏的参数--混合宏的不足\n\n混合宏在实际编码中给我们带来很多方便之处，特别是对于复用重复代码块。但其最大的不足之处是会生成冗余的代码块。比如在不同的地方调用一个相同的混合宏时。如：\n\n```scss\n@mixin border-radius{\n  -webkit-border-radius: 3px;\n  border-radius: 3px;\n}\n\n.box {\n  @include border-radius;\n  margin-bottom: 5px;\n}\n\n.btn {\n  @include border-radius;\n}\n```\n\n示例在“.box”和“.btn”中都调用了定义好的“border-radius”混合宏。先来看编译出来的 CSS：\n\n```scss\n.box {\n  -webkit-border-radius: 3px;\n  border-radius: 3px;\n  margin-bottom: 5px;\n}\n\n.btn {\n  -webkit-border-radius: 3px;\n  border-radius: 3px;\n}\n```\n\n上例明显可以看出，Sass 在调用相同的混合宏时，并不能智能的将相同的样式代码块合并在一起。这也是 Sass 的混合宏最不足之处。\n\n## [Sass]扩展\u002F继承\n\n继承对于了解 CSS 的同学来说一点都不陌生，先来看一张图：\n\n\n\n图中代码显示“.col-sub .block li,.col-extra .block li” 继承了 “.item-list ul li”选择器的 “padding : 0;” 和 “ul li” 选择器中的 “list-style : none outside none;”以及 * 选择器中的 “box-sizing:inherit;”。\n\n在 Sass 中也具有继承一说，也是继承类中的样式代码块。在 Sass 中是通过关键词 “@extend”来继承已存在的类样式块，从而实现代码的继承。如下所示：\n\n```scss\n.btn {\n  border: 1px solid #ccc;\n  padding: 6px 10px;\n  font-size: 14px;\n}\n\n.btn-primary {\n  background-color: #f36;\n  color: #fff;\n  @extend .btn;\n}\n\n.btn-second {\n  background-color: orange;\n  color: #fff;\n  @extend .btn;\n}\n```\n\n编译出来之后：\n\n```css\n.btn, .btn-primary, .btn-second {\n  border: 1px solid #ccc;\n  padding: 6px 10px;\n  font-size: 14px;\n}\n\n.btn-primary {\n  background-color: #f36;\n  color: #fff;\n}\n\n.btn-second {\n  background-clor: orange;\n  color: #fff;\n}\n```\n\n从示例代码可以看出，在 Sass 中的继承，可以继承类样式块中所有样式代码，而且编译出来的 CSS 会将选择器合并在一起，形成组合选择器：\n\n```css\n.btn, .btn-primary, .btn-second {\n  border: 1px solid #ccc;\n  padding: 6px 10px;\n  font-size: 14px;\n}\n```\n\n## [Sass]占位符 %placeholder\n\nSass 中的占位符 %placeholder 功能是一个很强大，很实用的一个功能，这也是我非常喜欢的功能。他可以取代以前 CSS 中的基类造成的代码冗余的情形。因为 %placeholder 声明的代码，如果不被 @extend 调用的话，不会产生任何代码。来看一个演示：\n\n```scss\n%mt5 {\n  margin-top: 5px;\n}\n%pt5{\n  padding-top: 5px;\n}\n```\n\n这段代码没有被 @extend 调用，他并没有产生任何代码块，只是静静的躺在你的某个 SCSS 文件中。只有通过 @extend 调用才会产生代码：\n\n```scss\n%mt5 {\n  margin-top: 5px;\n}\n%pt5{\n  padding-top: 5px;\n}\n\n.btn {\n  @extend %mt5;\n  @extend %pt5;\n}\n\n.block {\n  @extend %mt5;\n\n  span {\n    @extend %pt5;\n  }\n}\n```\n\n编译出来的CSS\n\n```css\n.btn, .block {\n  margin-top: 5px;\n}\n\n.btn, .block span {\n  padding-top: 5px;\n}\n```\n\n从编译出来的 CSS 代码可以看出，通过 @extend 调用的占位符，编译出来的代码会将相同的代码合并在一起。这也是我们希望看到的效果，也让你的代码变得更为干净。\n\n\n\n## [Sass]混合宏 VS 继承 VS 占位符\n\n初学者都常常纠结于这个问题“什么时候用混合宏，什么时候用继承，什么时候使用占位符？”其实他们各有各的优点与缺点，先来看看他们使用效果：\n\n**a) Sass 中的混合宏使用**\n\n```scss\n@mixin mt($var){\n  margin-top: $var;  \n}\n\n.block {\n  @include mt(5px);\n\n  span {\n    display:block;\n    @include mt(5px);\n  }\n}\n\n.header {\n  color: orange;\n  @include mt(5px);\n\n  span{\n    display:block;\n    @include mt(5px);\n  }\n}\n```\n\n编译出来的 CSS 见右侧结果窗口。\n\n**总结：**编译出来的 CSS 清晰告诉了大家，他不会自动合并相同的样式代码，如果在样式文件中调用同一个混合宏，会产生多个对应的样式代码，造成代码的冗余，这也是 CSSer 无法忍受的一件事情。不过他并不是一无事处，他可以传参数。\n\n**个人建议**：如果你的代码块中涉及到变量，建议使用混合宏来创建相同的代码块。\n\n**b) Sass 中继承**\n\n同样的，将上面代码中的混合宏，使用类名来表示，然后通过继承来调用：\n\n```scss\n.mt{\n  margin-top: 5px;  \n}\n\n.block {\n  @extend .mt;\n\n  span {\n    display:block;\n    @extend .mt;\n  }\n}\n\n.header {\n  color: orange;\n  @extend .mt;\n\n  span{\n    display:block;\n    @extend .mt;\n  }\n}\n```\n\n**总结：**使用继承后，编译出来的 CSS 会将使用继承的代码块合并到一起，通过组合选择器的方式向大家展现，比如 .mt, .block, .block span, .header, .header span。这样编译出来的代码相对于混合宏来说要干净的多，也是 CSSer 期望看到。但是他不能传变量参数。\n\n**个人建议**：如果你的代码块不需要专任何变量参数，而且有一个基类已在文件中存在，那么建议使用 Sass 的继承。\n\n**c) 占位符**\n\n最后来看占位符，将上面代码中的基类 .mt 换成 Sass 的占位符格式：\n\n```scss\n%mt{\n  margin-top: 5px;  \n}\n\n.block {\n  @extend %mt;\n\n  span {\n    display:block;\n    @extend %mt;\n  }\n}\n\n.header {\n  color: orange;\n  @extend %mt;\n\n  span{\n    display:block;\n    @extend %mt;\n  }\n}\n```\n\n**总结：**编译出来的 CSS 代码和使用继承基本上是相同，只是不会在代码中生成占位符 mt 的选择器。那么占位符和继承的主要区别的，“占位符是独立定义，不调用的时候是不会在 CSS 中产生任何代码；继承是首先有一个基类存在，不管调用与不调用，基类的样式都将会出现在编译出来的 CSS 代码中。”\n\n来看一个表格：\n\n[![img](http:\u002F\u002Fimg.mukewang.com\u002F55263aa30001913307940364.jpg)](http:\u002F\u002Fimg.mukewang.com\u002F55263aa30001913307940364.jpg)\n\n## [Sass]插值#{}\n\n使用 CSS 预处理器语言的一个主要原因是想使用 Sass 获得一个更好的结构体系。比如说你想写更干净的、高效的和面向对象的 CSS。Sass 中的插值(Interpolation)就是重要的一部分。让我们看一下下面的例子：\n\n```scss\n$properties: (margin, padding);\n@mixin set-value($side, $value) {\n    @each $prop in $properties {\n        #{$prop}-#{$side}: $value;\n    }\n}\n.login-box {\n    @include set-value(top, 14px);\n}\n```\n\n@each...in...语句会在《Sass进级篇》中 [1-6 @each循环](http:\u002F\u002Fwww.imooc.com\u002Fcode\u002F8811) 中讲解。\n\n它可以让变量和属性工作的很完美，上面的代码编译成 CSS：\n\n```css\n.login-box {\n    margin-top: 14px;\n    padding-top: 14px;\n}\n```\n\n这是 Sass 插值中一个简单的实例。当你想设置属性值的时候你可以使用字符串插入进来。另一个有用的用法是构建一个选择器。可以这样使用：\n\n```scss\n@mixin generate-sizes($class, $small, $medium, $big) {\n    .#{$class}-small { font-size: $small; }\n    .#{$class}-medium { font-size: $medium; }\n    .#{$class}-big { font-size: $big; }\n}\n@include generate-sizes(\\\"header-text\\\", 12px, 20px, 40px);\n```\n\n编译出来的 CSS:\n\n```css\n.header-text-small { font-size: 12px; }\n.header-text-medium { font-size: 20px; }\n.header-text-big { font-size: 40px; }\n```\n\n一旦你发现这一点，你就会想到超级酷的 mixins，用来生成代码或者生成另一个 mixins。然而，这并不完全是可能的。第一个限制，这可能会很删除用于 Sass 变量的插值。\n\n```scss\n$margin-big: 40px;\n$margin-medium: 20px;\n$margin-small: 12px;\n@mixin set-value($size) {\n    margin-top: $margin-#{$size};\n}\n.login-box {\n    @include set-value(big);\n}\n```\n\n上面的 Sass 代码编译出来，你会得到下面的信息：\n\nerror style.scss (Line 5: Undefined variable: “$margin-\\\".)\n\n所以，#{}语法并不是随处可用，你也不能在 mixin 中调用：\n\n```scss\n@mixin updated-status {\n    margin-top: 20px;\n    background: #F00;\n}\n$flag: \\\"status\\\";\n.navigation {\n    @include updated-#{$flag};\n}\n```\n\n上面的代码在编译成 CSS 时同样会报错：\n\nerror style.scss (Line 7: Invalid CSS after \\\"...nclude updated-\\\": expected \\\"}\\\", was \\\"#{$flag};\\\")\n幸运的是，可以使用 @extend 中使用插值。例如：\n\n```scss\n%updated-status {\n    margin-top: 20px;\n    background: #F00;\n}\n.selected-status {\n    font-weight: bold;\n}\n$flag: \\\"status\\\";\n.navigation {\n    @extend %updated-#{$flag};\n    @extend .selected-#{$flag};\n}\n```\n\n\n上面的 Sass 代码是可以运行的，因为他给了我们力量，可以动态的插入 .class 和 %placeholder。当然他们不能接受像 mixin 这样的参数，上面的代码编译出来的 CSS:\n\n```css\n.navigation {\n    margin-top: 20px;\n    background: #F00;\n}\n.selected-status, .navigation {\n    font-weight: bold;\n}\n```\n\n\n在 Sass 的社区正在积极讨论插值的局限性，谁又知道呢，也许我们很快将能够使用这些技术也说不定呢。\n\n\n\n## [Sass]注释\n\n注释对于一名程序员来说，是极其重要，良好的注释能帮助自己或者别人阅读源码。在 Sass 中注释有两种方式，我暂且将其命名为：\n\n1、类似 CSS 的注释方式，使用 ”\u002F* ”开头，结属使用 ”*\u002F ”\n2、类似 JavaScript 的注释方式，使用“\u002F\u002F”\n\n两者区别，前者会在编译出来的 CSS 显示，后者在编译出来的 CSS 中不会显示，来看一个示例：\n\n```scss\n\u002F\u002F定义一个占位符\n\n%mt5 {\n  margin-top: 5px;\n}\n\n\u002F*调用一个占位符*\u002F\n\n.box {\n  @extend %mt5;\n}\n```\n\n编译出来的CSS\n\n```css\n.box {\n  margin-top: 5px;\n}\n\n\u002F*调用一个占位符*\u002F\n```\n\n## [Sass]数据类型\n\n Sass 和 JavaScript 语言类似，也具有自己的数据类型，在 Sass 中包含以下几种数据类型：\n\n-  数字: 如，1、 2、 13、 10px；\n-  字符串：有引号字符串或无引号字符串，如，\\\"foo\\\"、 'bar'、 baz；\n-  颜色：如，blue、 #04a3f9、 rgba(255,0,0,0.5)；\n-  布尔型：如，true、 false；\n-  空值：如，null；\n-  值列表：用空格或者逗号分开，如，1.5em 1em 0 2em 、 Helvetica, Arial, sans-serif。\n\n\nSassScript 也支持其他 CSS 属性值（property value），比如 Unicode 范围，或 !important 声明。然而，Sass 不会特殊对待这些属性值，一律视为无引号字符串 (unquoted strings)。\n\n后两个小节详细讲解字符串和值列表数据类型，其它类型与JavaScript中的用法一致。[Sass]字符串\n\nSassScript 支持 CSS 的两种字符串类型：\n\n- 有引号字符串 (quoted strings)，如 \\\"Lucida Grande\\\" 、'http:\u002F\u002Fsass-lang.com'；\n- 无引号字符串 (unquoted strings)，如 sans-serifbold。\n\n在编译 CSS 文件时不会改变其类型。只有一种情况例外，使用 #{ }插值语句 (interpolation) 时，有引号字符串将被编译为无引号字符串，这样方便了在混合指令 (mixin) 中引用选择器名。\n\n```scss\n@mixin firefox-message($selector) {\n  body.firefox #{$selector}:before {\n    content: \\\"Hi, Firefox users!\\\";\n  }\n}\n@include firefox-message(\\\".header\\\");\n```\n\n编译为：\n\n```css\nbody.firefox .header:before {\n  content: \\\"Hi, Firefox users!\\\"; }\n```\n\n需要注意的是：当 deprecated = property syntax 时 （暂时不理解是怎样的情况），所有的字符串都将被编译为无引号字符串，不论是否使用了引号。\n\n## [Sass]值列表\n\n所谓值列表 (lists) 是指 Sass 如何处理 CSS 中： \n\n```css\nmargin: 10px 15px 0 0\n```\n\n或者： \n\n```scss\nfont-face: Helvetica, Arial, sans-serif\n```\n\n像上面这样通过空格或者逗号分隔的一系列的值。\n\n事实上，独立的值也被视为值列表——只包含一个值的值列表。\n\nSass列表函数（Sass list functions）赋予了值列表更多功能（Sass进级会有讲解）：\n\n1. nth函数（nth function） 可以直接访问值列表中的某一项；\n2. join函数（join function） 可以将多个值列表连结在一起；\n3. append函数（append function） 可以在值列表中添加值； \n4. @each规则（@each rule） 则能够给值列表中的每个项目添加样式。\n\n值列表中可以再包含值列表，比如 1px 2px, 5px 6px 是包含 1px 2px 与 5px 6px 两个值列表的值列表。如果内外两层值列表使用相同的分隔方式，要用圆括号包裹内层，所以也可以写成 (1px 2px) (5px 6px)。当值列表被编译为 CSS 时，Sass 不会添加任何圆括号，因为 CSS 不允许这样做。(1px 2px) (5px 6px)与 1px 2px 5px 6px 在编译后的 CSS 文件中是一样的，但是它们在 Sass 文件中却有不同的意义，前者是包含两个值列表的值列表，而后者是包含四个值的值列表。\n\n可以用 () 表示空的列表，这样不可以直接编译成 CSS，比如编译 font-family: ()时，Sass 将会报错。如果值列表中包含空的值列表或空值，编译时将清除空值，比如 1px 2px () 3px 或 1px 2px null 3px。\n\n## [Sass运算]加法\n\n程序中的运算是常见的一件事情，但在 CSS 中能做运算的，到目前为止仅有 calc() 函数可行。但在 Sass 中，运算只是其基本特性之一。在 Sass 中可以做各种数学计算，在接下来的章节中，主要和大家一起探讨有关于 Sass 中的数学运算。\n\n（一）、加法\n\n加法运算是 Sass 中运算中的一种，在变量或属性中都可以做加法运算。如：\n\n```scss\n.box {\n  width: 20px + 8in;\n}\n```\n\n编译出来的 CSS:\n\n```css\n.box {\n  width: 788px;\n}\n```\n\n但对于携带不同类型的单位时，在 Sass 中计算会报错，如下例所示：\n\n```css\n.box {\n  width: 20px + 1em;\n}\n```\n\n编译的时候，编译器会报错：“Incompatible units: 'em' and ‘px'.”\n\n## [Sass运算]减法\n\nSass 的减法运算和加法运算类似，我们通过一个简单的示例来做阐述：\n\n```scss\n$full-width: 960px;\n$sidebar-width: 200px;\n\n.content {\n  width: $full-width -  $sidebar-width;\n}\n```\n\n编译出来的 CSS 如下：\n\n```css\n.content {\n  width: 760px;\n}\n```\n\n同样的，运算时碰到不同类型的单位时，编译也会报错，如：\n\n```scss\n$full-width: 960px;\n\n.content {\n  width: $full-width -  1em;\n}\n```\n\n编译的时候，编译器报“Incompatible units: 'em' and ‘px’.”错误。\n\n## [Sass运算]乘法\n\nSass 中的乘法运算和前面介绍的加法与减法运算还略有不同。虽然他也能够支持多种单位（比如 em ,px , %），但当一个单位同时声明两个值时会有问题。比如下面的示例：\n\n```scss\n.box {\n  width:10px * 2px;  \n}\n```\n\n编译的时候报“20px*px isn't a valid CSS value.”错误信息。\n\n如果进行乘法运算时，两个值单位相同时，只需要为一个数值提供单位即可。上面的示例可以修改成：\n\n```scss\n.box {\n  width: 10px * 2;\n}\n```\n\n编译出来的 CSS:\n\n```\n.box {\n  width: 20px;\n}\n```\n\nSass 的乘法运算和加法、减法运算一样，在运算中有不同类型的单位时，也将会报错。如下面的示例：\n\n```scss\n.box {\n  width: 20px * 2em;\n}\n```\n\n编译时报“40em*px isn't a valid CSS value.”错误信息。\n\n\n\n## [Sass运算]除法\n\nSass 的乘法运算规则也适用于除法运算。不过除法运算还有一个特殊之处。众所周知“**\u002F**”符号在 CSS 中已做为一种符号使用。因此在 Sass 中做除法运算时，直接使用“\u002F”符号做为除号时，将不会生效，编译时既得不到我们需要的效果，也不会报错。一起先来看一个简单的示例：\n\n```scss\n.box {\n  width: 100px \u002F 2;  \n}\n```\n\n编译出来的 CSS 如下：\n\n```scss\n.box {\n  width: 100px \u002F 2;\n}\n```\n\n这样的结果对于大家来说没有任何意义。要修正这个问题，只需要给运算的外面添加一个小括号**( )**即可：\n\n```scss\n.box {\n  width: (100px \u002F 2);  \n}\n```\n\n编译出来的 CSS 如下：\n\n```scss\n.box {\n  width: 50px;\n}\n```\n\n除了上面情况带有小括号，“\u002F”符号会当作除法运算符之外，如果“\u002F”符号在已有的数学表达式中时，也会被认作除法符号。如下面示例：\n\n```scss\n.box {\n  width: 100px \u002F 2 + 2in;  \n}\n```\n\n编译出来的CSS：\n\n```scss\n.box {\n  width: 242px;\n}\n```\n\n另外，在 Sass 除法运算中，当用变量进行除法运算时，“\u002F”符号也会自动被识别成除法，如下例所示：\n\n```scss\n$width: 1000px;\n$nums: 10;\n\n.item {\n  width: $width \u002F 10;  \n}\n\n.list {\n  width: $width \u002F $nums;\n}\n```\n\n编译出来的CSS:\n\n```scss\n.item {\n  width: 100px;\n}\n\n.list {\n  width: 100px;\n}\n```\n\n综合上述，”\u002F ”符号被当作除法运算符时有以下几种情况：\n\n•   如果数值或它的任意部分是存储在一个变量中或是函数的返回值。\n•   如果数值被圆括号包围。\n•   如果数值是另一个数学表达式的一部分。\n\n如下所示：\n\n```scss\n\u002F\u002FSCSS\np {\n  font: 10px\u002F8px;             \u002F\u002F 纯 CSS，不是除法运算\n  $width: 1000px;\n  width: $width\u002F2;            \u002F\u002F 使用了变量，是除法运算\n  width: round(1.5)\u002F2;        \u002F\u002F 使用了函数，是除法运算\n  height: (500px\u002F2);          \u002F\u002F 使用了圆括号，是除法运算\n  margin-left: 5px + 8px\u002F2px; \u002F\u002F 使用了加（+）号，是除法运算\n}\n```\n\n编译出来的CSS\n\n```css\np {\n  font: 10px\u002F8px;\n  width: 500px;\n  height: 250px;\n  margin-left: 9px;\n }\n```\n\n\nSass 的除法运算还有一个情况。我们先回忆一下，在乘法运算时，如果两个值带有相同单位时，做乘法运算时，出来的结果并不是我们需要的结果。但在除法运算时，如果两个值带有相同的单位值时，除法运算之后会得到一个不带单位的数值。如下所示：\n\n```css\n.box {\n  width: (1000px \u002F 100px);\n}\n```\n\n编译出来的CSS如下：\n\n```css\n.box {\n  width: 10;\n}\n```\n\n## [Sass运算]变量计算\n\n在 Sass 中除了可以使用数值进行运算之外，还可以使用变量进行计算，其实在前面章节的示例中也或多或少的向大家展示了。在 Sass 中使用变量进行计算，这使得 Sass 的数学运算功能变得更加实用。一起来看一个简单的示例：\n\n```scss\n$content-width: 720px;\n$sidebar-width: 220px;\n$gutter: 20px;\n\n.container {\n  width: $content-width + $sidebar-width + $gutter;\n  margin: 0 auto;\n}\n```\n\n编译出来的CSS\n\n```css\n.container {\n  width: 960px;\n  margin: 0 auto;\n}\n```\n\n## [Sass运算]数字运算\n\n在 Sass 运算中数字运算是较为常见的，数字运算包括前面介绍的：加法、减法、乘法和除法等运算。而且还可以通过括号来修改他们的运算先后顺序。和我们数学运算是一样的，一起来看个示例。\n\n```scss\n.box {\n  width: ((220px + 720px) - 11 * 20 ) \u002F 12 ;  \n}\n```\n\n编译出来的 CSS:\n\n```css\n.box {\n  width: 60px;\n}\n```\n\n上面这个简单示例是一个典型的计算 Grid 单列列宽的运算。\n\n## [Sass运算]颜色运算\n\n所有算数运算都支持颜色值，并且是分段运算的。也就是说，红、绿和蓝各颜色分段单独进行运算。如：\n\n```\np {\n  color: #010203 + #040506;\n}\n```\n\n计算公式为 01 + 04 = 05、02 + 05 = 07 和 03 + 06 = 09， 并且被合成为：\n\n如此编译出来的 CSS 为：\n\n```\np {\n  color: #050709;\n}\n```\n\n算数运算也能将数字和颜色值 一起运算，同样也是分段运算的。如：\n\n```\np {\n  color: #010203 * 2;\n}\n```\n\n计算公式为 01 * 2 = 02、02 * 2 = 04 和 03 * 2 = 06， 并且被合成为：\n\n```\np {\n  color: #020406;\n}\n```\n\n \n\n## [Sass运算]字符运算\n\n在 Sass 中可以通过加法符号“+”来对字符串进行连接。例如：\n\n```scss\n$content: \\\"Hello\\\" + \\\"\\\" + \\\"Sass!\\\";\n.box:before {\n  content: \\\" #{$content} \\\";\n}\n```\n\n编译出来的CSS：\n\n```css\n.box:before {\n  content: \\\" Hello Sass! \\\";\n}\n```\n\n除了在变量中做字符连接运算之外，还可以直接通过 +，把字符连接在一起：\n\n```css\ndiv {\n  cursor: e + -resize;\n}\n```\n\n编译出来的CSS:\n\n```css\ndiv {\n  cursor: e-resize;\n}\n```\n\n注意，如果有引号的字符串被添加了一个没有引号的字符串 （也就是，带引号的字符串在 + 符号左侧）， 结果会是一个有引号的字符串。 同样的，如果一个没有引号的字符串被添加了一个有引号的字符串 （没有引号的字符串在 + 符号左侧）， 结果将是一个没有引号的字符串。 例如：\n\n```css\np:before {\n  content: \\\"Foo \\\" + Bar;\n  font-family: sans- + \\\"serif\\\";\n}\n```\n\n编译出来的 CSS：\n\n```css\np:before {\n  content: \\\"Foo Bar\\\";\n  font-family: sans-serif; }\n```","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F1656596640264.jpeg",787,"2022-06-30T13:44:28.000Z","2026-06-27T11:13:48.301Z",{"id":21,"name":22,"slug":23},{"id":293,"name":294,"avatar":287},[363],{"id":106,"name":22,"slug":23},{"list":365,"total":10,"page":12,"pageSize":366},[],20]