Ошибка Element implicitly has an 'any' type because expression of type 'string' can't be used to index type при использовании функции Sort()
Использую redux-toolkit, вот моя функция сортировки:
sortedPosts(state,action:PayloadAction<string>) {
state.filterPosts = state.posts.sort((a,b) => a[action.payload].localeCompare(b[action.payload]))
}
ts ругается, подчеркивая a[action.payload] и b[action.payload]: "Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'WritableDraft'. No index signature with a parameter of type 'string' was found on type 'WritableDraft'"
Что можно сделать, что-бы исправить проблему?
interface PostsState {
posts: IPosts[];
filterPosts: IPosts[];
isLoading: boolean;
error: string;
}
const initialState: PostsState = {
posts:[],
filterPosts:[],
isLoading: false,
error: ''
}
export const postSlice = createSlice({
name: 'posts',
initialState,
reducers: {
addPost(state, action:PayloadAction<IPosts>) {
state.filterPosts.unshift(action.payload)
},
removePost(state, action:PayloadAction<number>) {
state.filterPosts = state.filterPosts.filter(post => post.id !== action.payload)
},
searchPosts(state, action:PayloadAction<string>) {
state.filterPosts = state.posts.filter(post => post.title.toLowerCase().includes(action.payload))
},
sortedPosts(state,action:PayloadAction<string>) {
state.filterPosts = state.posts.sort((a,b) => a[action.payload].localeCompare(b[action.payload]))
}
},
extraReducers: {
[fetchPosts.fulfilled.type]:(state, action:PayloadAction<IPosts[]>) => {
state.isLoading = false;
state.error = '';
state.posts = action.payload;
state.filterPosts = action.payload;
},
[fetchPosts.pending.type]:(state) => {
state.isLoading = true;
},
[fetchPosts.rejected.type]:(state,action:PayloadAction<string>) => {
state.isLoading = false;
state.error = action.payload;
}
}
})
export interface IPosts {
id: number,
title: string,
body: string
}
Ответы (3 шт):
Добавьте в ваш IPost индекс
interface IPosts {
[index: string]: string | number
id: number,
title: string,
body: string
}
И callBack функции метода .sort надо будет добавить проверку, что тип данных по переданному ключу это строка
state.posts.sort((a, b) => {
if (typeof a[s] === 'string' && typeof b[s] === 'string')
return a[s].localeCompare(b[s])
return 0
})
можно воспользоваться keyof, позволяющий получить перечисление ключей из типа. Это позволит использовать переменную в качестве ключа.
sortedPosts(state,action:PayloadAction<keyof IPosts>) {
однако в этом случае возникает проблема в использовании localeCompare, который есть только у строк, однако в классе IPosts есть поле id с типом number.
Это пример показывающий как можно найти ошибку в типах.
Если гарантируется, что id не будет передан в эту функцию, можно указать это в типе, с помощью Exclude
Exclude<keyof IPosts, 'id'>
Либо сделать тип только со строковыми полями:
type StringProps<T> = {[key in keyof T as T[key] extends string ? key:never]: T[key]}
action:PayloadAction<keyof StringProps<IPosts>>
В этом случае typescript сможет проверить, что полученное значение точно будет string
Спасибо Grundy и SwaD за варианты, в итоге получилось вот так:
sortedPosts(state,action:PayloadAction<'title' | 'body'>) {
state.filterPosts = state.posts.sort((a, b) => {
if (typeof a[action.payload] === 'string' && typeof b[action.payload] === 'string')
return a[action.payload].localeCompare(b[action.payload])
return 0
})
}
Сам компонент сортировки (и поиска)
const PostFilter:FC = () => {
const [query, setQuery] = useState<string>('');
const [sort, setSort] = useState<string>('');
const dispatch = useDispatch();
useEffect(() => {
dispatch(searchPosts(query));
console.log('поиск!!!')
},[query])
useEffect(() => {
dispatch(sortedPosts(sort as 'title' | 'body'));
console.log('сортировка')
},[sort])
return (
<div className='postFilter'>
<MyInput
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder='Поиск...'
type='text'/>
<MySelect
value={sort}
onChange={e => setSort(e.target.value)}
defaultValue='сортировка'
options={[
{value: 'title', name: 'По названию'},
{value: 'body', name: 'По описанию'}
]}/>
</div>
)
}
Если я оставляю action в таком виде: action:PayloadAction<string>, то LocaleCompare выдает ошибку: Property 'localeCompare' does not exist on type 'string | number'.
Property 'localeCompare' does not exist on type 'number'
Поэтому я оставляю литеральные типы по которым буду сортировать: action:PayloadAction<'title' | 'body'>
В этом случае уже внутри самого компонента в блоке кода:
useEffect(() => {
dispatch(sortedPosts(sort));
console.log('сортировка')
},[sort])
Подчеркивается sort и ошибка: Argument of type 'string' is not assignable to parameter of type '"title" | "body"
Поэтому мне приходится приводить его к типам: dispatch(sortedPosts(sort as 'title' | 'body'));
Выглядит костыльненько...