如何應用 SOLID 原則整理 React 代碼之單一原則

本文爲譯文,原文鏈接:betterprogramming.pub/how-to-appl…

推薦語:該文章對於剛學習 React 的同學來說還是非常有幫助的,通過運用單一原則可以把組件劃分、邏輯劃分的代碼進行整理

SOLID 原則的主要是作爲關心自己工作的軟件專業人員的指導方針,另外還爲那些以經得起時間考驗的設計精美的代碼庫爲榮的人。

今天,我們將從一個糟糕的代碼示例開始,應用 SOLID 的第一個原則,看看它如何幫助我們編寫小巧、漂亮、乾淨的並明確責任的 React 組件,。

什麼是單一責任原則?

單一責任原則告訴我們的是,每個類或組件應該有一個單一的存在目的。

組件應該只做一件事,並且做得很好。

讓我們重構一段糟糕但正常工作的代碼,並使用這個原則使其更加清晰和完善。

讓我們從一個糟糕的例子開始

首先讓我們看看一些違反這一原則的代碼,添加註釋是爲了更好地理解:

import React, {useEffect, useReducer, useState} from "react";

const initialState = {
    isLoading: true
};


function reducer(state, action) {
    switch (action.type) {
        case 'LOADING':
            return {isLoading: true};
        case 'FINISHED':
            return {isLoading: false};
        default:
            return state;
    }
}

export const SingleResponsibilityPrinciple = () => {

    const [users , setUsers] = useState([])
    const [filteredUsers , setFilteredUsers] = useState([])
    const [state, dispatch] = useReducer(reducer, initialState);

    const showDetails = (userId) => {
        const user = filteredUsers.find(user => user.id===userId);
        alert(user.contact)
    }

    
    useEffect(() => {
        dispatch({type:'LOADING'})
        fetch('https://jsonplaceholder.typicode.com/users')
            .then(response => response.json())
            .then(json => {
                dispatch({type:'FINISHED'})
                setUsers(json)
            })
    },[])

    
    useEffect(() => {
        const filteredUsers = users.map(user => {
            return {
                id: user.id,
                name: user.name,
                contact: `${user.phone} , ${user.email}`
            };
        });
        setFilteredUsers(filteredUsers)
    },[users])

    
    return <>
        <div> Users List</div>
        <div> Loading state: {state.isLoading? 'Loading': 'Success'}</div>
        {users.map(user => {
            return <div key={user.id} onClick={() => showDetails(user.id)}>
                <div>{user.name}</div>
                <div>{user.email}</div>
            </div>
        })}
    </>
}

這段代碼的作用

這是一個函數式組件,我們從遠程數據源獲取數據,再過濾數據,然後在 UI 中顯示數據。我們還檢測 API 調用的加載狀態。

爲了更好地理解這個例子,我把它簡化了。但是你幾乎可以在任何地方的同一個組件中找到它們!這裏發生了很多事情:

  1. 遠程數據的獲取

  2. 數據過濾

  3. 複雜的狀態管理

  4. 複雜的 UI 功能

因此,讓我們探索如何改進代碼的設計並使其緊湊。

  1. 移動數據處理邏輯

不要將 HTTP 調用保留在組件中。這是經驗之談。您可以採用幾種策略從組件中刪除這些代碼。

您至少應該創建一個自定義 Hook 並將數據獲取邏輯移動到那裏。例如,我們可以創建一個名爲 useGetRemoteData 的 Hook,如下所示:

import {useEffect, useReducer, useState} from "react";

const initialState = {
    isLoading: true
};

function reducer(state, action) {
    switch (action.type) {
        case 'LOADING':
            return {isLoading: true};
        case 'FINISHED':
            return {isLoading: false};
        default:
            return state;
    }
}

export const useGetRemoteData = (url) => {

    const [users , setUsers] = useState([])
    const [state, dispatch] = useReducer(reducer, initialState);

    const [filteredUsers , setFilteredUsers] = useState([])


    useEffect(() => {
        dispatch({type:'LOADING'})
        fetch('https://jsonplaceholder.typicode.com/users')
            .then(response => response.json())
            .then(json => {
                dispatch({type:'FINISHED'})
                setUsers(json)
            })
    },[])

    useEffect(() => {
        const filteredUsers = users.map(user => {
            return {
                id: user.id,
                name: user.name,
                contact: `${user.phone} , ${user.email}`
            };
        });
        setFilteredUsers(filteredUsers)
    },[users])

    return {filteredUsers , isLoading: state.isLoading}
}

現在我們的主要組件看起來像這樣:

import React from "react";
import {useGetRemoteData} from "./useGetRemoteData";

export const SingleResponsibilityPrinciple = () => {

    const {filteredUsers , isLoading} = useGetRemoteData()

    const showDetails = (userId) => {
        const user = filteredUsers.find(user => user.id===userId);
        alert(user.contact)
    }

    return <>
        <div> Users List</div>
        <div> Loading state: {isLoading? 'Loading': 'Success'}</div>
        {filteredUsers.map(user => {
            return <div key={user.id} onClick={() => showDetails(user.id)}>
                <div>{user.name}</div>
                <div>{user.email}</div>
            </div>
        })}
    </>
}

看看我們的組件現在是多麼的小,多麼的容易理解!這是在錯綜複雜的代碼庫中所能做的最簡單、最重要的事情。

但我們可以做得更好。

  1. 可重用的數據獲取鉤子

現在,當我們看到我們 useGetRemoteData Hook 時,我們看到這個 Hook 正在做兩件事:

  1. 從遠程數據源獲取數據

  2. 過濾數據

讓我們把獲取遠程數據的邏輯提取到一個單獨的鉤子,這個鉤子的名字是 useHttpGetRequest,它把 URL 作爲一個參數:

import {useEffect, useReducer, useState} from "react";
import {loadingReducer} from "./LoadingReducer";

const initialState = {
    isLoading: true
};

export const useHttpGetRequest = (URL) => {

    const [users , setUsers] = useState([])
    const [state, dispatch] = useReducer(loadingReducer, initialState);

    useEffect(() => {
        dispatch({type:'LOADING'})
        fetch(URL)
            .then(response => response.json())
            .then(json => {
                dispatch({type:'FINISHED'})
                setUsers(json)
            })
    },[])

    return {users , isLoading: state.isLoading}

}

我們還將 reducer 邏輯移除到一個單獨的文件中:

export function loadingReducer(state, action) {
    switch (action.type) {
        case 'LOADING':
            return {isLoading: true};
        case 'FINISHED':
            return {isLoading: false};
        default:
            return state;
    }
}

所以現在我們的 useGetRemoteData 變成了:

import {useEffect, useState} from "react";
import {useHttpGetRequest} from "./useHttpGet";
const REMOTE_URL = 'https://jsonplaceholder.typicode.com/users'

export const useGetRemoteData = () => {
    const {users , isLoading} = useHttpGetRequest(REMOTE_URL)
    const [filteredUsers , setFilteredUsers] = useState([])

    useEffect(() => {
        const filteredUsers = users.map(user => {
            return {
                id: user.id,
                name: user.name,
                contact: `${user.phone} , ${user.email}`
            };
        });
        setFilteredUsers(filteredUsers)
    },[users])

    return {filteredUsers , isLoading}
}

乾淨多了,對吧? 我們能做得更好嗎? 當然,爲什麼不呢?

  1. 分解 UI 組件

看看我們的組件,其中顯示了用戶的詳細信息。我們可以爲此創建一個可重用的 UserDetails 組件:

const UserDetails = (user) => {

    const showDetails = (user) => {
        alert(user.contact)
    }

    return <div key={user.id} onClick={() => showDetails(user)}>
        <div>{user.name}</div>
        <div>{user.email}</div>
    </div>
}

最後,我們的原始組件變成:

import React from "react";
import {useGetRemoteData} from "./useGetRemoteData";

export const Users = () => {
    const {filteredUsers , isLoading} = useGetRemoteData()

    return <>
        <div> Users List</div>
        <div> Loading state: {isLoading? 'Loading': 'Success'}</div>
        {filteredUsers.map(user => <UserDetails user={user}/>)}
    </>
}

我們把代碼從 60 行精簡到了 12 行!我們創建了五個獨立的組成部分,每個部分都有明確而單一的職責。

讓我們回顧一下我們剛剛做了什麼

讓我們回顧一下我們的組件,看看我們是否實現了 SRP:

當然,我們可以改進很多其他的東西,但是這應該是一個很好的起點。

總結

這是一個簡單的演示,演示如何減少每個文件中的代碼量,並使用 SOLID 的強大功能創建漂亮的可重用組件。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://juejin.cn/post/6963480203637030926