📜  如何在 ReactJS 中使用单一职责原则?

📅  最后修改于: 2021-10-19 06:08:42             🧑  作者: Mango

如果您是一名开发人员,那么您肯定会在您的编程生涯中多次听过 SOLID 原则这个词。在软件开发中,SOLID 原则可作为开发人员的指导方针。无论您在项目中使用哪种语言,为了使您的代码干净且可维护,您需要在您的项目中应用 SOLID 原则。

SOLID 原则使开发人员更容易完成任务,也有助于他们维护项目中的代码。现在让我们谈谈 React,这是一个非常受开发人员欢迎的框架。

在 React的帮助下,您可以创建漂亮的 UI。在你职业生涯的早期阶段,你可能会在 React 中编写代码时犯很多错误,但是一旦你有了相关的工作经验,你就会明白在 React 中编写干净且可维护的代码也很重要。出于这个原因,肯定可以帮助您的一件事是 SOLID 原则。

您可以编写小巧、美观且干净的 React 组件。 SOLID 原则使您的组件可见且职责明确。 SOLID 原则告诉我们每个类都应该有一个存在的目的。在 React 中,组件一次只能做一件事。

ReactJS 中的如何使用单一职责原则

现在让我们了解如何在 React 中重构糟糕的代码并使其更清晰。首先,让我们考虑一个不好的例子……

Javascript
import React, {useEffect, useReducer, useState} from "react";
  
const initialState = {
    isLoading: true
};
  
// COMPLEX STATE MANAGEMENT
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)
    }
  
    // REMOTE DATA FETCHING
    useEffect(() => {
        dispatch({type:'LOADING'})
        fetch('https://jsonplaceholder.typicode.com/users')
            .then(response => response.json())
            .then(json => {
                dispatch({type:'FINISHED'})
                setUsers(json)
            })
    },[])
  
    // PROCESSING DATA
    useEffect(() => {
        const filteredUsers = users.map(user => {
            return {
                id: user.id,
                name: user.name,
                contact: `${user.phone} , ${user.email}`
            };
        });
        setFilteredUsers(filteredUsers)
    },[users])
  
    // COMPLEX UI RENDERING
    return <>
        
Users List
        
Loading state: {state.isLoading? 'Loading': 'Success'}
        {users.map(user => {             return
showDetails(user.id)}>                 
{user.name}
                
{user.email}
            
        })}      }


Javascript
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}
}


Javascript
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 <>
        
Users List
        
Loading state: {isLoading? 'Loading': 'Success'}
        {filteredUsers.map(user => {             return
showDetails(user.id)}>                 
{user.name}
                
{user.email}
            
        })}      }


Javascript
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}
  
}


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


Javascript
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}
}


Javascript
const UserDetails = (user) => {
  
    const showDetails = (user) => {
        alert(user.contact)
    }
  
    return 
showDetails(user)}>         
{user.name}
        
{user.email}
    
}


Javascript
import React from "react";
import {useGetRemoteData} from "./useGetRemoteData";
  
export const Users = () => {
    const {filteredUsers , isLoading} = useGetRemoteData()
  
    return <>
        
Users List
        
Loading state: {isLoading? 'Loading': 'Success'}
        {filteredUsers.map(user => )}      }


在这里,我们从远程源获取数据,然后在 UI 中呈现它。我们也在检测 API 调用的加载状态。基本上,上面的代码主要分为……四件事……

  • 远程数据获取…
  • 数据过滤…
  • 复杂的状态管理…
  • 复杂的用户界面功能…

现在让我们看看如何改进此代码的设计以及如何使其更易于清理……

1. 从代码中分离数据处理逻辑。

您永远不应该将 HTTP 调用保留在组件内。这是一个基本的经验法则。要从组件中删除这些代码,您可以遵循多种策略。

您可以创建自定义挂钩,并且可以将数据获取和过滤逻辑移动到该自定义挂钩中。让我们看看如何做到这一点……

创建一个名为 useGetRemoteData 的挂钩。看起来像下面…

Javascript

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}
}

现在,如果您查看主要组件,它将如下所示……

Javascript

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 <>
        
Users List
        
Loading state: {isLoading? 'Loading': 'Success'}
        {filteredUsers.map(user => {             return
showDetails(user.id)}>                 
{user.name}
                
{user.email}
            
        })}      }

您可以观察到您的组件现在更加清晰和易于理解。让我们使用更多的技术或方法使我们的代码更好。

2. 分离数据获取的代码,使其可重用

useGetRemoteData 在您的代码中有两个用途……

  1. 从远程源获取数据
  2. 过滤数据

我们可以制作一个单独的钩子,我们可以将我们的数据获取逻辑移到那里。让我们给它起个名字……useHttpGetRequest。它将 URL 作为一个组件。

Javascript

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 逻辑分离到一个单独的文件中……

Javascript

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

执行以上两个操作后……useGetRemoteData 如下图……

Javascript

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}
}

现在您可以观察到代码变得更加清晰。我们可以执行更多的操作,让这段代码变得更好。让我们看看如何做到这一点……

3. 分解 UI 组件

将用户详细信息的代码分离到不同的组件中,该组件仅负责显示用户详细信息。

Javascript

const UserDetails = (user) => {
  
    const showDetails = (user) => {
        alert(user.contact)
    }
  
    return 
showDetails(user)}>         
{user.name}
        
{user.email}
    
}

现在原始组件如下所示:

Javascript

import React from "react";
import {useGetRemoteData} from "./useGetRemoteData";
  
export const Users = () => {
    const {filteredUsers , isLoading} = useGetRemoteData()
  
    return <>
        
Users List
        
Loading state: {isLoading? 'Loading': 'Success'}
        {filteredUsers.map(user => )}      }

你有没有观察到你的代码太长了,现在太短了?我们只是将代码分解为五个独立的组件,然后将我们的逻辑放在那里。每个组件现在负责单一职责。

让我们回顾一下我们的代码,看看我们在这里做了什么。我们创建了五个不同的组件……

  • Users.js:负责展示用户列表。
  • UserDetails.js:负责显示用户的详细信息
  • useGetRemoteData.js:负责过滤远程数据
  • useHttpGetrequest.js:负责HTTP调用
  • LoadingReducer.js:复杂的状态管理。

希望你现在清楚了。