自定义 hook vue3 composable 的声明文件

declaration file for custom hook vue3 composable

提问人:Kauê Zuquetto 提问时间:10/11/2023 最后编辑:Kauê Zuquetto 更新时间:10/13/2023 访问量:58

问:

我有一个 laravel、vue3 和 typescript 项目。

在用户模块 UsersGrid 中,我有一个自定义可组合项 useUser ,我想输入它,但是我尝试的每一种方式,总是会出现错误

作为自定义钩子(可组合)我无法 npm install @types/XYZ

根目录中的 tsconfig:

{
  "compilerOptions": {
    // As long as you are using a build tool, we recommend you to author and ship in ES modules.
    // Even if you are targeting Node.js, because
    //  - `CommonJS` is too outdated
    //  - the ecosystem hasn't fully caught up with `Node16`/`NodeNext`
    // This recommendation includes environments like Vitest, Vite Config File, Vite SSR, etc.
    "module": "ESNext",

    // We expect users to use bundlers.
    // So here we enable some resolution features that are only available in bundlers.
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    // `allowImportingTsExtensions` can only be used when `noEmit` or `emitDeclarationOnly` is set.
    // But `noEmit` may cause problems with solution-style tsconfigs:
    // <https://github.com/microsoft/TypeScript/issues/49844>
    // And `emitDeclarationOnly` is not always wanted.
    // Considering it's not likely to be commonly used in Vue codebases, we don't enable it here.

    // Required in Vue projects
    "jsx": "preserve",
    "jsxImportSource": "vue",

    // `"noImplicitThis": true` is part of `strict`
    // Added again here in case some users decide to disable `strict`.
    // This enables stricter inference for data properties on `this`.
    "noImplicitThis": true,
    "strict": true,

    // <https://devblogs.microsoft.com/typescript/announcing-typescript-5-0/#verbatimmodulesyntax>
    // Any imports or exports without a type modifier are left around. This is important for `<script setup>`.
    // Anything that uses the type modifier is dropped entirely.
    "verbatimModuleSyntax": true,

    // A few notes:
    // - Vue 3 supports ES2016+
    // - For Vite, the actual compilation target is determined by the
    //   `build.target` option in the Vite config.
    //   So don't change the `target` field here. It has to be
    //   at least `ES2020` for dynamic `import()`s and `import.meta` to work correctly.
    // - If you are not using Vite, feel free to overwrite the `target` field.
    "target": "ESNext",
    // For spec compilance.
    // `true` by default if the `target` is `ES2020` or higher.
    // Explicitly set it to `true` here in case some users want to overwrite the `target`.
    "useDefineForClassFields": true,

    // Recommended
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    // See <https://github.com/vuejs/vue-cli/pull/5688>
    "skipLibCheck": true,
  }
}

用户网格:

<template>
    <div>
        <ag-grid-vue :style="gridStyle" suppressHorizontalScroll="true" :gridOptions="gridOptions" :class="gridMode"
            :columnDefs="columnDefs.value" :rowData="rowData" :loadingOverlayComponent="CardOverlay">
        </ag-grid-vue>
    </div>
</template> 

<script setup lang="ts">

import { ref, watch, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router';
import { AgGridVue } from "ag-grid-vue3";
import { useI18n } from 'vue-i18n'
import { gridOptions, gridStyle } from "../../helpers/gridSettings";
import ActionButtons from '../../components/grids/ActionButtons.vue'
import CardOverlay from '../../components/general/CardOverlay.vue'
import Tr from "../../helpers/translation";
import useUser from '../../composables/useUser' // Could not find a declaration file for module '../../composables/useUser'. 'c:/Users/kaue.zuquetto/Desktop/Dev/Portal/resources/js/composables/useUser.js' implicitly has an 'any' type.
import { useStore } from 'vuex'

import type { User, ColDef } from '../../../../types'



const { t, locale } = useI18n()
const store = useStore()
const router = useRouter()
const gridMode = ref('ag-theme-alpine')
const columnDefs = ref()
const {
    getAllData: getUsers,
    deleteData: deleteUser,
    rowData,
} = useUser();

const details = (id: number) => {
    router.push(Tr.i18nRoute({ name: 'user.read', params: { id } }))
}

const i18nGrid = () => {
    columnDefs.value = [
        [],
        { headerName: 'ID', field: 'id', flex: 1 },
        { headerName: t('user.grid.colName'), field: 'name', flex: 3 },
        { headerName: t('user.grid.colEmail'), field: 'email', flex: 3 },
        { headerName: t('grid.created_at'), field: 'created_at', flex: 3 },
        { headerName: t('grid.updated_at'), field: 'updated_at', flex: 3 },
        {
            headerName: '',
            flex: 1.5,
            sortable: false,
            filter: false,
            cellRenderer: ActionButtons,
            cellRendererParams: function () {
                return {
                    details: details,
                    delete: deleteUser,
                    id: arguments[0].data.id,
                    buttons: ['details', 'delete']
                }
            },
        },
    ] as ColDef<User>[];
}

const options = computed(() => {
    return store.getters["preferences/options"]
})

watch(options, () => {
    gridMode.value = store.state.preferences.options.grid
})

watch(locale, () => {
    i18nGrid()
})

onMounted(() => {
    i18nGrid()
    getUsers()
    gridMode.value = store.state.preferences.options.grid
})

</script>

我的使用用户可组合:

import { ref, reactive } from 'vue'
import { useRouter } from 'vue-router'
import useSwal from '../helpers/swal'
import Tr from '../helpers/translation'
import { gridOptions, gridStyle } from "../helpers/gridSettings"

const { confirm_delete_swal, toast_success, toast_error } = useSwal()

const useUser = (userID = null) => {

    const router = useRouter()
    const user = ref({})
    const isLoading = ref(false)
    const processing = ref(false)
    const errors = ref(false)
    const rowData = ref(null)

    const getAllData = async () => {
        try {
            const response = await axios.get('/api/users')
            const { status } = response
            if (status === 200) {
                rowData.value = response.data.data
                rowData.value.sort((a, b) => b.id - a.id)
            } else {
                console.log('ERROR STATUS:  ' + response.status)
                toast_error()
            }
        } catch (e) {
            gridOptions.api.hideOverlay()
            gridOptions.api.showNoRowsOverlay()
            toast_error()
            console.log(e)
        }
    }

    const getData = async () => {
        try {
            isLoading.value = true
            const url = '/api/user/' + userID + '/read';
            const response = await axios.get(url)
            if (response.status === 200) {
                const { data } = response.data
                user.value = data
            } else {
                console.log('ERROR STATUS:  ' + response.status)
                toast_error()
            }
        } catch (e) {
            toast_error()
            console.log(e)
        } finally {
            isLoading.value = false
        }
    }

    const createData = async (formData) => {
        try {
            processing.value = true
            const response = await axios.post('/api/user/create', formData)
            if (response.status === 200) {
                const { data } = response.data
                errors.value = {}
                router.push(Tr.i18nRoute({ name: 'user.read', params: { id: data.id } }))
                toast_success()
            } else {
                toast_error()
                console.log('ERROR STATUS:  ' + response.status)
            }
        } catch (e) {
            toast_error()
            console.log(e)
            if (e.response && e.response.data.errors) {
                errors.value = e.response.data.errors
            }
        } finally {
            processing.value = false
        }
    }

    const updateData = async (formData, userRoles) => {
        try {
            processing.value = true
            const response = await axios.put('/api/user/' + userID + '/update', formData)
            if (response.status === 200) {
                const { data } = response.data
                errors.value = {}
                router.replace(Tr.i18nRoute({ name: 'user.read', params: { id: data } }))
                userRoles.assignRoles()
            } else {
                toast_error()
                console.log('ERROR STATUS:  ' + response.status)
            }
        } catch (e) {
            toast_error()
            console.log(e)
            toast_error()
            if (e.response && e.response.data.errors) {
                errors.value = e.response.data.errors
            }
        } finally {
            processing.value = false
        }
    }

    const deleteData = async (id) => {
        try {
            const { isConfirmed } = await confirm_delete_swal(id)
            if (isConfirmed) {
                const response = await axios.delete('/api/user/' + id + '/delete')
                const { status } = response
                if (status == 200) {
                    getAllData()
                    toast_success()
                } else {
                    toast_error()
                }
            }
        } catch (e) {
            toast_error()
            console.log(e)
            if (e.response && e.response.data.errors) {
                errors.value = e.response.data.errors
            }
        }
    }

    userID ? getData() : ''

    return {
        getAllData,
        rowData,
        getData,
        user,
        createData,
        updateData,
        deleteData,
        isLoading,
        processing,
        errors,
    }
}
export default useUser;

如何从我的 useUser 钩子创建声明文件?它总是抱怨不是一个模块,或者不可调用

useUser.d.ts :

找不到模块 '.. 的声明文件。/../composables/useUser“。隐式具有“任何”type.ts(7016)

declare module 'useUser' {
    export function useUser(): unknown
  }
打字稿 拉拉维尔 vuejs3 可组合

评论


答:

1赞 yoduh 10/13/2023 #1

除非另有配置,否则 TypeScript 文件(或组件)仅需要类型化导入。您的可组合项是一个文件。您可以更改此行为并允许使用 tsconfig 中的 allowJs 编译器选项导入.js

"compilerOptions": {
     "allowJs": true,
}

或者,您可以在 TypeScript 中重写可组合项。

评论

0赞 Kauê Zuquetto 10/16/2023
我明白,但是在某些库的情况下,它是如何工作的?例如,我使用vue-i18n,有vue-i18n.d.js和vue-i18n.d.ts的文件,一个只有一个声明(声明文件),另一个只有JS,我怎样才能做出这样的声明文件?是否可以在不将 useUser 文件迁移到 .ts 的情况下?提前感谢您的帮助
0赞 yoduh 10/16/2023
大多数 TS 库也与 JS 兼容,因为编译步骤将打字稿拆分为 .js 和 .d.ts 文件,这些文件是发布的。这两个文件可以在 TS 项目中一起使用,也可以在 JS 项目中单独使用 .js 文件。通常,只需在 TS 中编写代码并让编译步骤创建声明文件会更容易,但您也可以根据需要手动创建一个声明文件,或者使用工具
0赞 Kauê Zuquetto 10/17/2023
非常感谢,感谢您抽出宝贵时间帮助我,我设法更好地理解,我认为必须同时拥有两种类型的文件。现在我要将 js 迁移到 ts