Logux has built-in TypeScript support. It exports type definitions. We even uses type tests with check-dts
since we have some tricky types with generics.
Server
We recommend to use typescript-fsa
and share actions between client and server.
// client/actions/users.ts
import { actionCreatorFactory } from 'typescript-fsa'
let createAction = actionCreatorFactory()
export const renameUser = createAction<{
userId: string,
name: string
}>('user/rename')
// modules/users/index.ts
import type { BaseServer } from '@logux/server'
import { renameUser } from '../../client/actions/users'
export default (server: BaseServer) => {
server.type(renameUser, {
access (ctx, action, meta) {
// TypeScript will know that action must have `userId` key
return action.payload.userId === ctx.userId
},
…
})
}
You can define types for ctx.params
in subscriptions:
type UserParams = {
id: string
}
server.channel<UserParams>('user/:id', {
access (ctx, action, meta) {
return ctx.params.id === ctx.userId
}
})
Client
We recommend to use typescript-fsa
or similar library for typed action creators.
// actions/users.ts
import { actionCreatorFactory } from 'typescript-fsa'
export const renameUser = createAction<{
userId: string,
name: string
}>('user/rename')
// reducers/user.ts
import { renameUser } from '../actions/users'
type User = {
id: string,
name: string
}
export type UsersState = User[]
function reducer (state: UsersState = [], action: Action): UsersState {
if (renameUser.match(action)) {
return state.map(user => {
if (user.id === action.payload.userId) {
return { ...user, name: action.payload.name }
} else {
return user
}
})
}
return state
}
// store.ts
import { UserRenameAction } from '../actions/users'
import { PostAddAction } from '../actions/users'
import { UsersState } from '../reducers/users'
import { PostsState } from '../reducers/users'
export type State = {
posts: PostsState,
user: UsersState
}
export type Actions = UserRenameAction ||
PostAddAction
let client = new CrossTabClient({ … })
let createStore = createStoreCreator(client, { … })
let store = createStore<State, Actions>(reducer)
// src/store/index.ts
type User = {
id: string,
name: string
}
export type State = {
users: User[]
}
let client = new CrossTabClient({ … })
let createStore = createStoreCreator(client, { … })
let store = createStore<State>({
state: {
users: []
},
mutations: {
…
'user/rename': (state, action) => {
state.users = state.users.map(user => {
if (user.id === action.userId) {
return { ...user, name: action.name }
} else {
return user
}
})
}
}
})
store.commit.sync({
type: 'user/rename',
userId: '10',
name: 'Tom'
})
For useStore
composable function you can provide State
as a generic:
import { useStore } from '@logux/vuex'
import { State } from '../store/index.js'
export default {
setup () {
let store = useStore<State>()
}
}
Place the following code in your project to allow this.$store to be typed correctly:
// src/shims-vuex.d.ts
import { LoguxVuexStore } from '@logux/vuex'
import { State } from './store/index.js'
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
$store: LoguxVuexStore<State>
}
}
You need to define user-defined type guards for action types:
import { Action } from '@logux/core'
type UserRenameAction = Action & {
type: 'user/rename',
userId: string,
name: string
}
app.log.type<UserRenameAction>('user/rename', action => {
document.title = action.name
})