项目中遇到的问题


大纲链接 §

[toc]


推荐阅读 Vite真香之路 问题记录

pnpm add -g pnpm安装路径提示错误 无法升级 The location of the currently running pnpm differs from the location where pnpm will be installed

复现

  • 运行 pnpm add -g pnpm
  • 会报如下错误:
1
2
3
4
Nothing to stop. No server is running for the store at C:\Users\XXX\AppData\Local\pnpm\store\v3
The location of the currently running pnpm differs from the location where pnpm will be installed
 Current pnpm location: C:\Users\XXX\AppData\Roaming\npm
 Target location: C:\Users\XXX\AppData\Local\pnpm

解决

  • 使用npm install -g pnpm

参考


build遇到报错 The service was stopped: write EPIPE

  • failed to load config from xxx\vite.config.ts error during build:
  • Error: The service was stopped: write EPIPE
  • 重启服务即可 抽风,同端口号3000被占用
    • 管理员模式 打开终端 运行 net stop winnat && net start winnat

如何在生命周期中恰当地处理异步与异步传递数据

  • created
  • mounted
  • 组件间传递数据

登录注册 错误时也跳转

  • 已解决 bug

解决 使用tsx语法 使用slot插槽报错 [Vue warn]: Slot "default" invoked outside of the render function: this will not track dependencies used in the slot. Invoke the slot function inside the render function instead.

  • 请求先后
  • 先获取总的数据 前一个生命周期 onBeforeMount

参考


ts 动态声明字符串模板变量类型

1
2
3
4
5
type X = '/blog/:blogId' | `/blog/${number}`

const xx: X = '/blog/:blogId'; // right
const xx: X = '/blog/11'; // right
const xx: X = '/blog/blogId'; // wrong

样式设置了overflow-x: hidden;导致了window.scrollTo失效

@/components/BlogBody.tsx

  • 记录当前滚动位置 const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
  • 记录初始/上一次滚动位置 oldScrollTop.value = scrollTop;,初始为const oldScrollTop = ref(0);
  • 记录对比当前和上一次滚动位置差值 const scrollStep = scrollTop - oldScrollTop.value;
  • scrollStep 为正 向下 隐藏导航栏;为负 向上 显示导航栏
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import {defineComponent, onMounted, onUnmounted,} from 'vue';

// props
const BlogBodyProps = {
  isSHow: Boolean
};

export default defineComponent({
  name: 'BlogBody',
  props: BlogBodyProps,
  components: {},
  emits: ['showHeaderFooter'],
  setup(props, ctx) {
    // 获取 节点,非实时响应式
    const main = ref();

    const oldScrollTop = ref(0);

    // 按滚动方向自动显示/隐藏 回调
    const scrollingCB = () => {
      // 滚动条距文档顶部的距离(做兼容处理)
      // 注意是对象 document.documentElement 的属性 scrollTop
      // 监听 window.document 的元素 documentElement.scrollTop 属性值变化
      const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
      // 滚动条滚动的距离
      const scrollStep = scrollTop - oldScrollTop.value;
      // 更新——滚动前,滚动条距文档顶部的距离
      oldScrollTop.value = scrollTop;
      // 关键 scrollStep 为正 向下 隐藏导航栏;
      // 为负 向上 显示导航栏
      scrollStep < 0
        ? (ctx.emit('showHeaderFooter', true))
        : (ctx.emit('showHeaderFooter', false));
    };

    onMounted(() => {
      window.document.addEventListener('scroll', scrollingCB);
    });

    onUnmounted(() => {
      window.document.removeEventListener('scroll', scrollingCB);
    });

    return {
      main,
    };
  },
  render() {
    return (
      <main ref={(el) => this.main = el}>
        <router-view/>
      </main>
    );
  }

});


@/components/Layout.tsx

@/styles/layout.module.scss

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@import '../src/assets/style/variables';

.layout {
  align-items: center;
  display: grid;
  grid-template-areas: 'header header header'
                       '.      main  .'
                       'footer footer footer';
  grid-template-columns: 12% auto 12%;
  grid-template-rows: auto 1fr auto;
  height: 100vh;
  justify-content: center;
  //overflow-x: hidden; // 会导致 document.documentElement.scrollTop 始终为 0 的 bug

  ::-webkit-scrollbar {
    display: none;
  }

  scroll-behavior: smooth;
  width: 100vw;

  .blog-header {...}

  .blog-main {...}

  .blog-footer {...}

}

参考



报错[Vue warn]: Unhandled error during execution of render function ref=Ref< undefined >

  • render()函数中的数据在组件创建时,直接调用beautifyDate()时还未初始化,导致报错
  • 解决方法:在调用异步方法getBlogDetail()内部,执行方法beautifyDate(),并赋值给已定义的响应式数据createdAt.value
  • render()函数中使用this.createdAt而不是直接调用beautifyDate()

css modules 中如何使用 深入组件样式?

  • 想使用jsx写法编写组件的时候,无法使用scoped这个用法
  • 因为jsx等同于一个js文件,里面可写不了template与style标签
  • tsx直接使用字符串return <div class="test">,会导致样式文件会挂载到全局的style标签上,且类名没有任何转译,这样就会容易造成样式污染
  • 要覆盖子组件内部的元素样式

cover-ant.scss

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
input.ant-input:focus,
textarea.ant-input:focus,
input.ant-input:hover,
textarea.ant-input:hover {
  border-color: $theme-lighter-color;
}

ul.ant-pagination {
  > li.ant-pagination-item,
  li.ant-pagination-prev,
  li.ant-pagination-next {
    > a.ant-pagination-item-link {

      > span.anticon {
        transform: translateY(55%);
      }
    }

    a:not(.ant-pagination-item-link) {
      display: inline-block;
      verticle-align: -.6em;
    }
  }
}

在路由中使用 defineAsyncComponent动态引入组件

原来@/router/index.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
/* 不使用defineAsyncComponent 动态加载组件出现警告 component: () => import('@/pages/blog/index/BlogIndex')
静态导入 */
// components
import BlogIndex from '@/pages/blog/index/BlogIndex';
import Login from '@/pages/login/Login';
import Register from '@/pages/register/Register';
import CreateBlog from '@/pages/blog/create/CreateBlog';
import EditBlog from '@/pages/blog/edit/EditBlog';
import BlogDetail from '@/pages/blog/detail/BlogDetail';
import User from '@/pages/user/User';
import MyBlog from '@/pages/myBlog/MyBlog';
import About from '@/pages/About';

const routes: RouteRecordRaw[] = [
  {
    path: '/',
    name: 'BlogIndex',
    // import('') 必须是静态字符串,不可动态拼接
    // The above dynamic import cannot be analyzed by vite
    component: defineAsyncComponent(() => import('@/pages/blog/index/BlogIndex')), // 注意这里如果是.vue文件必须要带上 文件后缀.vue
  },
  {
    path: '/login',
    name: 'Login',
    component: Login,
  },
  {
    path: '/register',
    name: 'Register',
    component: Register,
  },
  {
    path: '/detail/:blogId',
    name: 'BlogDetail',
    component: BlogDetail,
  },
  {
    path: '/create',
    name: 'CreateBlog',
    component: CreateBlog,
    // 只有经过身份验证的用户才能创建帖子
    meta: {requiresAuth: true},
  },
  {
    path: '/edit/:blogId',
    name: 'EditBlog',
    component: EditBlog,
    meta: {requiresAuth: true},
  },
  {
    path: '/user/:userId',
    name: 'User',
    component: User,
    meta: {requiresAuth: true},
  },
  {
    path: '/myblog',
    name: 'MyBlog',
    component: MyBlog,
    meta: {requiresAuth: true},
  },
  {
    path: '/about',
    name: 'About',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: About
  }
];
...
  • import('xxx') 必须是静态字符串,不可动态拼接
  • 否则报错The above dynamic import cannot be analyzed by vite

改为使用 defineAsyncComponent API

  • 返回promise
    • defineAsyncComponent(() => {return new Promise((resolve, reject) => {...})})
  • 使用import函数作为返回值:
    • defineAsyncComponent(() => (import('./components/MyComponent.vue')))
  • vue3 Async Components

@/router/index.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
...
const routes: RouteRecordRaw[] = [
  {
    path: '/',
    name: 'BlogIndex',
    // import('') 必须是静态字符串,不可动态拼接
    // The above dynamic import cannot be analyzed by vite
    component: defineAsyncComponent(() => import('@/pages/blog/index/BlogIndex')), // 注意这里如果是.vue文件必须要带上 文件后缀.vue
  },
  ...
]
...
  • 使用defineAsyncComponent 动态加载组件时,会出现警告
1
[Vue Router warn]: Component "default" in record with path "/" is defined using "defineAsyncComponent()". Write "() => import('./MyPage.vue')" instead of "defineAsyncComponent(() => import('./MyPage.vue'))".
  • 代替原来的静态引入import BlogIndex from '@/pages/blog/index/BlogIndex';
  • 直接动态引入() => import('./MyPage.vue'))

@/router/index.ts

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import {createRouter, /*createWebHistory*/ createWebHashHistory, RouteRecordRaw} from 'vue-router';
import {storeToRefs} from 'pinia';
import useStore from '@/store';
import useAuthStore from '@/store/modules/auth';

const routes: RouteRecordRaw[] = [
  {
    path: '/',
    name: 'BlogIndex',
    // import('') 必须是静态字符串,不可动态拼接
    component: () => import('@/pages/blog/index/BlogIndex'),
  },
  {
    path: '/login',
    name: 'Login',
    component: () => import('@/pages/login/Login')
  },
  {
    path: '/register',
    name: 'Register',
    component: () => import('@/pages/register/Register'),
  },
  {
    path: '/detail/:blogId',
    name: 'BlogDetail',
    component: () => import('@/pages/blog/detail/BlogDetail'),
  },
  {
    path: '/create',
    name: 'CreateBlog',
    component: () => import('@/pages/blog/create/CreateBlog'),
    // 只有经过身份验证的用户才能创建帖子
    meta: {requiresAuth: true},
  },
  {
    path: '/edit/:blogId',
    name: 'EditBlog',
    component: () => import('@/pages/blog/edit/EditBlog'),
    meta: {requiresAuth: true},
  },
  {
    path: '/user/:userId',
    name: 'User',
    component: () => import('@/pages/user/User'),
    meta: {requiresAuth: true},
  },
  {
    path: '/myblog',
    name: 'MyBlog',
    component: () => import('@/pages/myBlog/MyBlog'),
    meta: {requiresAuth: true},
  },
  {
    path: '/about',
    name: 'About',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import('@/pages/About')
  }
];

const router = createRouter({
  history: createWebHashHistory(),
  routes,
});

// 路由全局前置守卫
router.beforeEach((to, from, next) => {
  // 匹配路由元信息
  // 判断是需要登录
  // const {getIsLogin,} = storeToRefs(useAuthStore()); // 使用 store.checkLogin() 服务器验证 的登录状态 代替

  const store = useAuthStore();
  /*
  * if(to.path === 'login') return next();
  * if (to.path 受控页面或 未登录) return next('/login?');
  * next()
  * */

  const ifRequiresAuth: boolean = to.matched.some(record => { return record.meta.requiresAuth; });

  // URL 是否需要 身份验证
  ifRequiresAuth
    ? (// 需要身份验证的 URL
      store.checkLogin() // 向服务器请求,获取当前登录状态
        .then((isLogin) => {
          !isLogin
            ? next({path: '/login', query: {redirect: to.fullPath}})
            : next(); // 服务器响应验证已登录
        }))
    : next(); // 不需要身份验证的 URL;
  /* 确保最后执行且只一次 next() */

  // 判断是否进入 注册 或 登录 的路由,先检查是否已登录
  /*
  ;['/register', '/login'].includes(to.path)
    ? (console.log('向服务器发送请求,验证登录'))
    : (console.log('不做任何处理', isLogin));
*/

});

// 路由全局后置守卫
router.afterEach((/* to, from, failure */) => {
  // console.log('路由全局后置守卫', to, from);

  // 清除 记录的 router-view 中的 组件名
  const {routerCompName,} = storeToRefs(useStore());
  routerCompName.value = '';
});

export default router;

参考与相关类似问题


报错[Vue warn]: Invalid VNode type: undefined (undefined)

  • 识别不出节点的类型
  • 是由于我在JSX中写了不合法的注释节点{{/*xxxxx*/}}

自定义组件 JSX 事件

使用自定义事件

  • 要声明一个自定义事件:onHandleSubmit
  • 子组件
    • 声明选项emits: ['handleSubmit',],
    • setup选项中传参setup(props, ctx) {}
      • 定义 发布方法 const clickHandler = () => {ctx.emit('handleSubmit');}
      • 返回return {clickHandler}
    • render方法中使用render() {return <button onClick={this.clickHandler}></button>}
  • 父组件
    • setup选项中
      • 定义 处理逻辑的方法 const clickHandler = () => {}
    • render方法中使用自定义事件 <UserSubmitBtnTip onHandleSubmit={this.clickHandler}/>

判断键盘抬起时,键盘值

  • 自定义组件中有<input/>,事件keyup不能绑定在自定义组件上,而是绑定在内部的的<input/>
  • 通过props传一个keyUpHandler到内部
  • 无修饰符,只能使用event: KeyboardEvent中的event.key来判断,是否按下Enter
    • ["Enter"].includes(event.key)

UserInput.tsx

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import {defineComponent,} from 'vue';
import cssAuth from '@/styles/auth.module.scss';
import {Input} from 'ant-design-vue';

const UserInputProps = {
  title: String,
  inputType: {type: String, default: ''},
  placeholder: String,
  errorText: String,
  keyUpHandler: Function
};

export default defineComponent({
  name: 'UserInput',
  props: UserInputProps,
  components: {},
  setup(/*props, ctx*/) {

    return {};
  },
  render() {
    return (
      <div>
        <label for={this.title}>
          <h4 class={cssAuth.password}>
            {this.title}
          </h4>
          <Input type={this.inputType}
                 placeholder={this.placeholder ?? this.title}
                 id={this.title}
                 class={cssAuth.userInput}
                 onKeyup={(e: KeyboardEvent) => {this.keyUpHandler && this.keyUpHandler();}}/>
        </label>
        <p class={cssAuth.error}>
          {this.errorText}
        </p>
      </div>
    );
  }

});

Login.tsx

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import {defineComponent, ref} from 'vue';
import router from '@/router';
import useAuthStore from '@/store/modules/auth';
import {logString} from '@/store/modules/auth/interface';
import cssAuth from '@/styles/auth.module.scss';
import UserInput from '@/components/user-authentication/UserInput';
import UserSubmitBtnTip from '@/components/user-authentication/UserSubmitBtnTip';

export default defineComponent({
  name: 'Login',
  props: {},
  components: {},
  setup(/*props, ctx*/) {
    const store = useAuthStore();
    // const router = useRouter();

    const username = ref('');
    const password = ref('');

    const asyncLogin = (logString: logString) => {
      return store.login(logString);
    };

    const onLogin = (logString: logString) => {
      asyncLogin(logString)
        .then(() => {
          return router.push({path: '/'});
        });
    };

    return {
      username,
      password,
      asyncLogin,
      onLogin
    };
  },
  render() {
    return (
      <section class={cssAuth.login}>
        <UserInput title="用户名"
                   errorText="当前用户名已注册"
                   v-model:username={this.username}/>

        <UserInput title="密码"
                   inputType="password"
                   errorText="当前用户名或密码不匹配"
                   v-model:password={this.password}
                   keyUpHandler={(e: KeyboardEvent) => {
                     ['Enter'].includes(e.key) && this.onLogin({username: this.username, password: this.password});
                   }}/>

        <UserSubmitBtnTip btnName="立即登录"
                          tipText="没有账号?"
                          linkTo="/register"
                          linkText="注册新用户"
                          onClick={() => {
                            this.onLogin({username: this.username, password: this.password});
                          }}/>
      </section>
    );
  }

});

参考

JSX不支持在setup方法中写defineEmits

  • 必须换成选项写法emits: ['xxx', 'update:xxx']

vite2 jsx/tsx 写vue提示:React is not defined


🚑 解决引入 module 类型报错

Vuex4 typescript 中自动将modules的类型添加到state中

pinia 遇坑

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { createRouter } from 'vue-router'

const router = createRouter({
  // ...
})

// ❌ Depending on the order of imports this will fail
const store = useStore()

router.beforeEach((to, from, next) => {
  // we wanted to use the store here
  if (store.isLoggedIn) next()
  else next('/login')
})

// Good way below

router.beforeEach((to) => {
  // ✅ This will work because the router starts its navigation after
  // the router is installed and pinia will be installed too
  const store = useStore()

  if (to.meta.requiresAuth && !store.isLoggedIn) return '/login'
})

@at-root 使用css预处理器写css时缩小嵌套层级

嵌套过深

  • css是从右到左解析的,所以层数越多对于解析会越不利
  • 后代选择器会增加样式权重,造成样式权重越来越高的恶性循环
  • 滥用 less sass 的嵌套写法会让代码不易于阅读

别让你的嵌套层级超过四层,不然就重构

  • 在Sass中还提供了一个@at-root 的功能,可以让你直接跳出去。这样也可以避免嵌套层级过深
  • 命名方式上可以解决,如:BEM
  • 提取公用逻辑

@at-root

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 首先定义通用混入样式
@mixin root($name) {
    @at-root .#{$name} {
        @content;
    }
}

// 随处使用
@include root(video-player) {

    @include root(video-player-core) {

    }

    @include root(video-player-bar) {

        .e-volume{}
        .e-play{}
        .e-fullscreen{

            @include root(fullscreen-xxx) {
                
                .x-date{}
                .x-test{}
            }
        }
    }
}

相似问题:前端路由多层级嵌套太深且重复的问题


使用引入的组件时报错TS2604: JSX element type 'XXX' does not have any construct or call signatures.

  • 是因为组件未使用defineComponent
1
2
3
4
5
<script lang="ts">
export default {
  ...
};
</script>

改为以下示例,即可

1
2
3
4
5
6
7
<script lang="ts">
import {defineComponent} from 'vue';

export default defineComponent({
 ...
});
</script>

网站搜索只能搜到react的相关内容

https://stackoverflow.com/questions/53449101/typescript-type-definition-ts2604-jsx-element-type-something-does-not-have-a | javascript - Typescript type definition: TS2604: JSX element type ‘something’ does not have any construct or call signatures - Stack Overflow https://segmentfault.com/q/1010000040209283 | 我想在.ts文件中使用JSX.Element对象,要怎么写。 - SegmentFault 思否 https://www.jianshu.com/p/ece127ffc996 | (转载)使用TS报错:TS7026: JSX element implicitly has type ‘any’…处理总结 - 简书 https://stackoverflow.com/questions/46080760/error-ts2604-jsx-element-type-does-not-have-any-construct-or-call-signatu | reactjs - Error TS2604: JSX element type ‘…’ does not have any construct or call signatures - Stack Overflow https://stackoverflow.com/questions/31815633/what-does-the-error-jsx-element-type-does-not-have-any-construct-or-call | reactjs - What does the error “JSX element type ‘…’ does not have any construct or call signatures” mean? - Stack Overflow https://stackoverflow.com/questions/54143658/typescript-ts2604-jsx-element-type-drawer-does-not-have-any-construct-or-call | reactjs - Typescript TS2604: JSX element type ‘Drawer’ does not have any construct or call signatures - Stack Overflow https://stackoverflow.com/questions/53452966/typescript-3-jsx-element-type-component-does-not-have-any-construct-or-call-s | javascript - TypeScript 3: JSX element type ‘Component’ does not have any construct or call signatures. [2604] - Stack Overflow https://stackoverflow.com/questions/34971155/typescript-error-ts2604-how-to-fix | reactjs - Typescript Error: TS2604 - How to fix? - Stack Overflow https://github.com/Microsoft/TypeScript-React-Starter/issues/44 | error TS2604: JSX element type ‘Provider’ does not have any construct or call signatures. · Issue #44 · microsoft/TypeScript-React-Starter https://www.imooc.com/wenda/detail/629578 | 用于从字符串创建 JSX 元素的正确 TypeScript 类型_慕课猿问


setup()中写return () => (JsxDOM)和在render() {return (JsxDOM)}选项中写

  • setup()中会覆盖掉render() {return (JsxDOM)}

IDE WebStorm 警告 在未导入 React 的情况下使用 JSX


引入无props的组件时,报错

  • .tsx文件
1
2
3
4
5
6
render: () => {
...
    const BlogHeaderDOM = <BlogHeader></BlogHeader>
    const BlogFooterDOM = <BlogFooter></BlogFooter>
    ...
}
  • TS2322: Type '{}' is not assignable to type 'IntrinsicAttributes & (Partial<{ [x: number]: string; } | {}> & Omit<(readonly string[] | Readonly<ExtractPropTypes<Readonly<ComponentObjectPropsOptions<Data>>>>) & (VNodeProps & ... 2 more ... & Readonly<...>), never>)'.   Type '{}' is not assignable to type 'IntrinsicAttributes & Partial<{}> & Omit<(readonly string[] | Readonly<ExtractPropTypes<Readonly<ComponentObjectPropsOptions<Data>>>>) & (VNodeProps & ... 2 more ... & Readonly<...>), never>'.     Type '{}' is missing the following properties from type 'Omit<(readonly string[] | Readonly<ExtractPropTypes<Readonly<ComponentObjectPropsOptions<Data>>>>) & (VNodeProps & AllowedComponentProps & ComponentCustomProps & Readonly<...>), never>': concat, indexOf, lastIndexOf, slice, and 17 more.

解决方法

  • 添加props

tsx文件中写点击事件绑定方法时<Button onClick={xxx}>...</Button> 报类型错误

  • ... is not assignable to type '(((event: MouseEvent) => void) & ((...args: any[]) => any)) | undefined'.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import {Button, message} from 'ant-design-vue';
import {defineComponent, inject} from 'vue';
import 'ant-design-vue/dist/antd.css';

export default defineComponent({
  name: 'BlogIndex',
  props: {},
  components: {},
  setup(/*props, ctx*/) {
    const popMessage = inject<typeof message>('$message');
    // noinspection JSXNamespaceValidation
/*
    return () => (
      <Button onClick={()=> {popMessage && popMessage.error('这是一条错误消息', 2)}}>博客首页</Button>
    );
    */
      return () => (
        <Button onClick={popMessage && popMessage.error.bind(this, '这是一条错误消息', 2)}>博客首页</Button>
      )
  },
  /*
    render() {
      // noinspection JSXNamespaceValidation
      return (
        <>
          <Button onClick={this.popMessage && this.popMessage.error.bind(this, '这是一条错误消息', 2)}>博客首页</Button>
        </>
      );
    }
  */

});

错误的范例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import {Button, message} from 'ant-design-vue';
import {defineComponent, inject} from 'vue';
import 'ant-design-vue/dist/antd.css';

export default defineComponent({
  name: 'BlogIndex',
  props: {},
  components: {},
  setup(/*props, ctx*/) {
    const popMessage = inject<typeof message>('$message');
    const popMessageError = (content: string, duration: number, fn?: (() => void)): void => {
      popMessage && popMessage.error(content, duration, fn);
    };

    return {
      popMessageError
    };
  },
  render() {
    // noinspection JSXNamespaceValidation
    return (
      <>
        <Button onClick={this.popMessageError.bind(this, '这是一条错误消息', 2)}>
          博客首页
        </Button>
      </>
    );
  }

});

vant UI的写法

  • <a onClick={(event: MouseEvent) => emit('click-thumb', event)}>...
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
...
render() {
    // noinspection JSXNamespaceValidation
    return (
      <>
        <Button onClick={({/*event: MouseEvent*/}) => {
          // this.popMessage && this.popMessage.error.bind(this, '这是一条错误消息', 2)
          this.popMessage && this.popMessage.error('这是一条错误消息', 2);
        }}>
          博客首页
        </Button>
      </>
    );
  }
...

项目根目录不支持驼峰式命名

  • 使用短杠-连接

端口号3000被占用

Windows 杀死占用端口程序的脚本:

  1. 使用 netstat -aon| findstr 3000 找到对应进程
  2. 使用 taskkill /F /PID 8888 杀死对应进程

误删了.idea文件

  • .idea文件时webStorm项目的配置文件,删除会导致IDE识别文件目录,和布局错误
  • 如果误删了.idea文件,可以点击文件菜单中的 修复IDE选项,根据事件日志中的提示,点选即可,重新加载项目目录

只用一种运行器 yarn | pnpm

  • pnpm为首选,一律使用pnpm
  • yarn,混用时,yarn依赖优先