依赖倒置原则(DIP)
始终具有抽象的高级代码接口,而不是实现细节
DIP 告诉我们,我们应该始终通过抽象与低级细节进行交互,从而“将布线隐藏在墙后”。[13]这与上面详述的 SRP 和 ISP 有很强的联系。实际上,对于 React 前端,这意味着我们高级代码中的函数不应该关心特定任务是如何完成的。例如,假设我们想调用一个 API 来获取待办事项列表——就像我们在上面的 SRP 部分中所做的一样:
const TodosPage = () => {
const todos = useTodos();
const renderTodos = () => {
return todos.map(todo => {
return <Todoitem id={todo.id} title={todo.title} />
});
};
return (
<div>
<h1>My Todos:</h1>
<ul>
{renderTodos()}
</ul>
</div>
)
};
关心那些是如何或从哪里来的吗?不!它只与隐藏了很多内部连接的函数交互。这使我们的代码更易于阅读,因为我们可以一眼看出函数的用途,以及它在整个组件中的使用方式。这与 SRP 密切相关,因为当我们从功能/组件中提取功能时,我们必然使我们的高级代码接口具有抽象。如果我们想改变获取这些待办事项的方式(例如使用API 而不是),我们可以在不进入组件的情况下做到这一点。<TodosPage />
todos
useTodos()
useTodos()
<TodosPage />
fetch
axios
<TodosPage />
function useTodos(){
const [todos, setTodos] = useState([]);
useEffect(() => {
// Refactored to use fetch() instead of axios.get() to call an API
async function getTodosWithFetch() {
const response = await fetch("https://jsonplaceholder.typicode.com/todos");
const data = await response.json();
setTodos(data);
};
getTodosWithFetch();
}, []);
return todos;
};
我们更改了实现细节(我们如何完成特定任务),但是我们不必更改. 事实上,我们可以更进一步。<TodosPage />
// useTodos.js
import localTodos from "./todos.json";
function useTodos(){
const data = localTodos.todos;
return todos;
};
export default useTodos;
// TodosPage.js
import useTodos from "./useTodos.js";
const TodosPage = () => {
const todos = useTodos();
const renderTodos = () => {
return todos.map(todo => {
return <Todoitem id={todo.id} title={todo.title} />
});
};
return (
<div>
<h1>My Todos:</h1>
<ul>
{renderTodos()}
</ul>
</div>
)
};
现在,我们甚至不调用 API 来获取我们的待办事项!todos.json
相反,我们从一些名为;的本地文件中读取它们。我们已经疯狂地更改了一个实现细节,但是,因为我们只依赖于函数的抽象来处理实际获取待办事项的细节,所以我们不必更改.useTodos()
<TodosPage />
抽象层
我们怎么知道什么时候我们已经远离“高级”代码而开始担心实现细节了?好吧,罗伯特·马丁再次给出了一些见解:
“函数有一个基本规则:函数的每一行都应该处于同一抽象级别,并且该级别应该低于函数名称。” - Robert C Martin,干净的代码,第 1 课 [5]
这意味着我们应该慢慢地抽象出功能,直到我们达到足够低的水平。这是一个有点模糊的定义,依赖于程序员的经验来了解每个概念的高级程度。抽象不够远的一个例子可能是:
const TodosList = () => {
const [todos, setTodos] = useState([]);
const [term, setTerm] = useState("");
useEffect(() => {
async function getTodos() {
const { data } = await axios.get("https://jsonplaceholder.typicode.com/todos/");
const filtered = data.filter(todo => todo.completed === false);
const pattern = new RegExp(term, "g");
const searched = filtered.filter(todo => pattern.test(todo.title));
setTodos(searched);
};
getTodos();
}, [term]);
const renderTodos = () => {
return todos.map(todo => {
return (
<li>
{`ID: ${todo.id}, Title: ${todo.title}`}
</li>
)
});
};
return(
<div>
<input value={term} onChange={(e) => setTerm(e.target.value)} />
<ul>
{renderTodos()}
</ul>
</div>
);
}
在这里,我们有很多实现细节隐藏在. 这使得很难判断发生了什么。然而,通过一些好的函数命名和一些抽象,我们可以解决这个问题。<TodosPage />
const TodosList = () => {
const [term, setTerm] = useState("");
const todos = useTodos(term);
const renderTodos = () => {
return todos.map(todo => {
return (
<li>
{`ID: ${todo.id}, Title: ${todo.title}`}
</li>
)
});
};
return(
<div>
<input value={term} onChange={(e) => setTerm(e.target.value)} />
<ul>
{renderTodos()}
</ul>
</div>
);
};
function useTodos(term){
const [todos, setTodos] = useState([]);
useEffect(() => {
async function getTodos() {
const { data } = await axios.get("https://jsonplaceholder.typicode.com/todos/");
const filtered = data.filter(todo => todo.completed === false);
const pattern = new RegExp(term, "g");
const searched = filtered.filter(todo => pattern.test(todo.title));
setTodos(searched);
};
getTodos();
}, [term]);
return todos;
};
所以,这要好一些——我们已经从中提取了细节,并最终得到了与我们之前提出的类似的解决方案。但是,我们似乎有一个重载的功能。我们有一些相对较高层次的抽象概念(例如网络请求)与相对较低层次的概念(例如模式匹配或数组操作)具有相同的功能。正如 Robert Martin 所说,“这很粗鲁!程序员在一行中将你从高度[高级概念]带到深度[低级概念] 。” . [5]或许我们可以再分解一下:<TodosPage />
useTodos()
function useTodos(term){
const [todos, setTodos] = useState([]);
useEffect(() => {
async function getTodos() {
const { data } = await axios.get("https://jsonplaceholder.typicode.com/todos/");
const filteredAndMatchedTodos = filterAndMatchTodos(data, term);
setTodos(filteredAndMatchedTodos);
};
getTodos();
}, [term]);
return todos;
};
function filterAndMatchTodos(todoList, searchTerm){
const completedTodos = todoList.filter(todo => todo.completed === false);
const pattern = new RegExp(searchTerm, "g");
const matchingTodos = filtered.filter(todo => pattern.test(todo.title));
return matchingTodos;
}
好的,现在看起来好多了。我们的函数已经从中提取了低级过滤/正则表达式匹配工作。这使得一切都处于相似的抽象级别;它处理网络请求和一些 React 状态管理,但不关心模式匹配等更底层的细节。此外,我们的功能对更广泛的网络请求或 React 不感兴趣;它只关心它会收到一个待办事项数组,它应该过滤已完成的待办事项,然后根据给定的搜索词对已完成的待办事项进行模式匹配。这已经使我们的整个程序更易于测试,因为我们已经将数组操作与网络请求分离。useTodos()
useTodos()
filterAndMatchTodos()
我们可以开始进一步抽象...
function filterAndMatchTodos(todoList, searchTerm){
const completedTodos = filterTodoList(todoList);
const matchingTodos = matchFilteredTodos(completedTodos, searchTerm);
return matchingTodos;
}
function filterTodoList(todoList){
return todoList.filter(todo => todo.completed === false);
};
function matchFilteredTodos(compeltedTodos, searchTerm){
const pattern = new RegExp(searchTerm, "g");
const matchingTodos = compeltedTodos.filter(todo => pattern.test(todo.title));
return matchingTodos;
};
...然而,在某些时候,必须对事情何时走得太远做出判断。也许上面的内容适合你,或者你觉得它在抽象层上走得太远了(例如,实际上filterTodoList
只是调用函数,因此在我看来为此创建一个新函数似乎是多余的)。作为回应我多次重复的信息:这些是指南;在对您的代码产生积极影响的范围内使用它们。Array.filter()
发表评论
所有评论(0)