我创建了一个自定义挂钩,如下所示:
import { getUserInfo } from '@/scripts/services/userService';
import { useState, useEffect } from 'react';
const useFileFilter = (repositoryFiles: any[]) => {
let enabledFilters: string[] = [];
let disabledFilters: string[] = [];
const [filteredRepositoryFiles, setFilteredRepositoryFiles] = useState([]);
useEffect(() => {
const filterFiles = async () => {
let filteredFiles: object[] = [];
const userInfo = await getUserInfo();
for (const filter of userInfo.showSourceFiles) {
if (filter.bool) {
enabledFilters.push(`.${filter.name}`);
} else {
disabledFilters.push(`.${filter.name}`);
}
}
if (enabledFilters.length === 0) {
enabledFilters = disabledFilters;
disabledFilters = [];
}
const fileExtentionsFilter = {
include: enabledFilters,
exclude: disabledFilters,
};
for (const file of repositoryFiles) {
const included = fileExtentionsFilter.include.some(fileExtentions =>
file.name.endsWith(fileExtentions),
);
const excluded = fileExtentionsFilter.exclude.some(fileExtentions =>
file.name.endsWith(fileExtentions),
);
if (included && !excluded && !file.isDirectory) {
filteredFiles.push(file);
}
}
console.log('works: ', filteredFiles);
setFilteredRepositoryFiles(filteredFiles);
}
filterFiles();
}, [repositoryFiles]);
console.log('doesnt work: ', filteredRepositoryFiles);
return filteredRepositoryFiles;
};
export default useFileFilter;
我正在尝试为它创建一个测试,如下所示:
import { describe, test, vi } from 'vitest';
import useFileFilter from '@/hooks/useFileFilter';
import { renderHook, waitFor } from '@testing-library/react';
import * as userService from '@/scripts/services/userService';
describe('For the useFileFilter hook', () => {
const fetchSpy = vi.spyOn(userService, "getUserInfo");
beforeAll(() => {
const mockResolveValue = {
"id": 1,
"email": "[email protected]",
"role": "user",
"showSourceFiles": [
{
"id": "cm4v6cj260003mpetpyicw2jm",
"name": "txt",
"bool": false,
"usersId": 1
},
{
"id": "cm4v6cj260004mpetznm7rpj6",
"name": "py",
"bool": false,
"usersId": 1
},
{
"id": "cm4v6cj260001mpet4z81kmcm",
"name": "gitignore",
"bool": true,
"usersId": 1
},
{
"id": "cm4v6cj260002mpetxe6csa1j",
"name": "yml",
"bool": true,
"usersId": 1
}
],
"firstName": null,
"lastName": null,
"avatarUrl": null,
"githubUsername": "SomeUser",
"githubAccessToken": "a_token"
}
fetchSpy.mockReturnValue(mockResolveValue as any);
});
afterAll(() => {
fetchSpy.mockRestore();
});
it('should return filtered repository files', async () => {
const repositoryFiles = [
{
"id": 1,
"isDirectory": false,
"path": ".gitignore",
"name": ".gitignore",
"lineCount": 161,
"functionalLineCount": 80,
"symlinkTarget": "",
"branchId": 1
},
{
"id": 2,
"isDirectory": false,
"path": "Dockerfile",
"name": "Dockerfile",
"lineCount": 8,
"functionalLineCount": 7,
"symlinkTarget": "",
"branchId": 1
},
{
"id": 3,
"isDirectory": false,
"path": "compose.yml",
"name": "compose.yml",
"lineCount": 18,
"functionalLineCount": 17,
"symlinkTarget": "",
"branchId": 1
},
...
]
const result = renderHook(() => useFileFilter(repositoryFiles));
console.log('result: ', result);
});
当我运行测试时,它会产生空结果:
result: {
result: { current: [] },
rerender: [Function: rerender],
unmount: [Function: unmount]
}
请注意第一个代码块中的console.logs。他们产生以下结果:
doesnt work: []
works: [
{
id: 1,
isDirectory: false,
path: '.gitignore',
name: '.gitignore',
lineCount: 161,
functionalLineCount: 80,
symlinkTarget: '',
branchId: 1
},
{
id: 3,
isDirectory: false,
path: 'compose.yml',
name: 'compose.yml',
lineCount: 18,
functionalLineCount: 17,
symlinkTarget: '',
branchId: 1
}
]
因此,由于某种原因,测试运行良好,但是当它尝试使用
FilteredRepositoryFiles
更新 setFilteredRepositoryFiles
的状态时,状态保持为空。尽管在测试之外使用此功能时效果很好。有谁知道为什么吗?
PS:反应很新
你不应该像在 React 中那样操作数组。让我们参考一下文档。
请阅读:
您不需要效果来转换数据以进行渲染。例如, 假设您想在显示列表之前对其进行过滤。你可能会 很想编写一个 Effect 来更新状态变量,当 列表更改。然而,这是 效率低下...
在这种情况下,一个 useMemo 就足够了,但是,因为有一个 api 调用,所以我们必须使用 useCallback。由于我们需要调用 useCallback,我们还将使用 useState 来存储结果,并使用 useEffect 在需要时执行操作(当存储库文件更改时):
import { useCallback, useEffect, useMemo, useState } from 'react';
import { getUserInfo } from '@/scripts/services/userService';
const useFileFilter = (repositoryFiles: any[]) => {
const [filteredRepositoryFiles, setFilteredRepositoryFiles] = useState();
const filterRepositoryFiles = useCallback(async () => {
let enabledFilters: string[] = [];
let disabledFilters: string[] = [];
let filteredFiles: object[] = [];
const userInfo = await getUserInfo();
for (const filter of userInfo.showSourceFiles) {
if (filter.bool) {
enabledFilters.push(`.${filter.name}`);
} else {
disabledFilters.push(`.${filter.name}`);
}
}
if (enabledFilters.length === 0) {
enabledFilters = disabledFilters;
disabledFilters = [];
}
const fileExtentionsFilter = {
include: enabledFilters,
exclude: disabledFilters,
};
for (const file of repositoryFiles) {
const included = fileExtentionsFilter.include.some(
(fileExtentions) => file.name.endsWith(fileExtentions),
);
const excluded = fileExtentionsFilter.exclude.some(
(fileExtentions) => file.name.endsWith(fileExtentions),
);
if (included && !excluded && !file.isDirectory) {
filteredFiles.push(file);
}
}
return filteredFiles;
}, [repositoryFiles]);
useEffect(() => {
//Only call when repositoryFiles has content
if (repositoryFiles.length > 0)
filterRepositoryFiles().then((v) => setFilteredRepositoryFiles(v));
}, [filterRepositoryFiles, repositoryFiles]);
return filteredRepositoryFiles;
};
export default useFileFilter;