React 項目結構和組件命名之道

摘要: > * 原文地址:[structuring projects and naming components in react](https://hackernoon.com/structuring-projects-and-naming-components-in-react-1261b6e18d76) > * 原文做者:[Vinicius Dacal](https://hackernoon.cocss

React 做爲一個庫,不會決定你如何組織項目的結構。這是件好事,由於這樣咱們有了充分的自由去嘗試不一樣的組織方式而且選取最適合咱們的方式。可是從另外一個角度講,這可能會讓剛剛上手 React 的開發者產生些許困惑。react

我將會在本文爲你們展現我已經使用過一段時間而且效果不錯的方式,這些方式沒有經過從新造輪子來實現,而是經過將社區中的方案組合和提煉獲得。git

注意:這裏沒有什麼是絕對正確的!你能夠選擇你認爲容易理解,而且能夠適應或能夠改形成適應你的情景的方式。github

目錄結構

我最多見到的一個問題就是如何組織文件和目錄結構。在本文中,咱們假設你有一個 create-react-app 生成的最簡單的目錄結構。json

create-react-app 爲咱們生成了一個基礎的項目,包含根目錄還有諸如.gitignorepackage.jsonREADME.mdyarn.lock 的文件。react-router

它還生成了 public 和 src 目錄,src 目錄就是咱們存放源代碼的目錄。app

看一下下面圖片所描述的結構:dom

本文咱們只會關注 src 目錄,全部在它以外的都會保持不變。編輯器

容器和組件

咱們能夠看到在 src 目錄下有 containers 目錄和 components 目錄:函數

src
├─ components 
└─ containers

可是這個方式會致使下面這些問題:

  • 主觀的規則 你不清楚什麼是容器而什麼是組件,這二者的差別是主觀定義的。若是在你的團隊裏去推行,讓全部的開發者可以相同地同意和評判這二者是很是困難的。
  • 沒有考慮組件的變化 即便你決定了一個組件適用於某種特定的種類,在項目的週期內很容易發生變化,最終迫使你把它從 components 挪到 containers 目錄下,反之亦然。
  • 容許兩個組件使用同一個名字 組件在應用中的命名應該是聲明式且惟一的,從而避免對相同命名組件的職責產生困惑。但上面的方式爲兩個組件能夠擁有相同命名打開了一個缺口,一個能夠是容器,另外一個能夠是展現型組件。
  • 效率低下 即便你在實現一個獨立特性時,也不得不常常在 containers 和 components 目錄下來回切換,由於一個獨立特性有兩種不一樣類型的組件是再正常不過的事情了。

有一種基於這種方式的變種方式,在模塊的目錄下保持着兩個目錄的分離。

想象一下在你的應用中有一個 User 模塊,在此模塊下,你有兩個目錄去分離你的組件:

src
└─ User
  ├─ components
  └─ containers

上述方式最小化了在兩個遙遠目錄下不斷切換的的問題,可是一樣增長了不少煩惱。當你的應用有很是多模塊的時候,你最終會可能會建立幾十個 containers 和 components 目錄。

因此咱們討論如何組織目錄和文件的時候,和組件是否被拆分爲展現型和容器型是無關的。也就是說,咱們會把全部的組件都放在 components 目錄下,除了頁面。

即便在目錄上拆分它們是沒必要要的,瞭解它們之間的差別性依然是有必要的。若是你對這個話題還有疑問,建議閱讀這篇文章:Presentational and Container Components

拆分和組合代碼

在 components 目錄下,咱們經過模塊/特性(module/feature)的結構來組織文件。

在對用戶進行增刪改查的過程當中,咱們只會有一個 User 模塊。因此咱們的目錄結構會像下面這樣:

src
└─ components
  └─ User
    ├─ Form.jsx
    └─ List.jsx

每當一個組件會有不止一個文件的時候,咱們會將這個組件和它對應的文件放在同一個文件夾下,而且使用同一個名字來命名。舉個例子:如今咱們有一個 Form.css 文件包含了 Form.jsx 的樣式,這時咱們的目錄結構會像這樣:

src
└─ components
  └─ User
    ├─ Form
    │ ├─ Form.jsx
    │ └─ Form.css
    └─ List.jsx

測試用的文件和被測試的文件放在一塊兒,在上面這個例子中,Form.jsx 的測試文件會放在同一個文件夾下而且命名爲 Form.spec.jsx

UI 組件

除了經過模塊拆分組件,咱們還會在 src/components 放置一個 UI 目錄,用於存放全部通用的組件。

UI 組件不屬於任何一個模塊,須要足夠通用。它們應該能夠直接放在開源庫中,由於它們不包含任何特定應用的業務邏輯。常見的這類組件有:按鈕,輸入框,複選框,下拉選擇,模態框,數據可視化組件等等。

組件命名

以上咱們瞭解瞭如何組織目錄結構和如何經過模塊來拆分咱們的組件,可是還有一個問題:如何命名它們?

這裏咱們說的是如何命名咱們的 class 或者定義組件的常量。

class MyComponent extends Component {}
const MyComponent = () => {};

組件的命名在應用中應當清晰且惟一,這樣可讓它們能夠輕鬆被找到而且避免可能的困惑。

當應用在運行時發生錯誤或者經過 React 開發者工具調試時,組件的名字是很是方便易用的,由於錯誤發生的地方每每都伴隨着組件的名字。

咱們採用基於路徑的組件命名方式,即根據相對於 components 文件目錄的相對路徑來命名,若是在此文件夾之外,則使用相對於 src 目錄的路徑。舉個例子,組件的路徑若是是 components/User/List.jsx,那麼它就被命名爲 UserList

若是文件名和文件目錄名相同,咱們不須要重複這個名字。也就是說,components/User/Form/Form.jsx 會命名爲 UserForm 而不是 UserFormForm

這樣的命名方式有如下幾點好處:

便於在項目中搜索文件

若是你的編輯器支持模糊搜索,只須要搜索 UserForm 就可讓你找到對應的文件:

若是你想要在目錄樹中搜索文件,能夠很容易地經過組件的名字定位到它:

能夠避免在引入時重複名稱

遵循這種方式,你能夠根據組件的上下文環境來命名文件。想一下上面的 form 組件,咱們知道它是一個 User 模塊下的 form 組件,可是既然咱們已經把 form 組件放在了 User 模塊的目錄下,咱們就不須要在 form 組件的文件名上重複 user 這個單詞,使用 Form.jsx 就能夠了。

我最初使用 React 的時候喜歡用完整的名字來命名文件,可是這樣會致使相同的部分重複太屢次,同時引入時的路徑太長。來看看這兩種方式的區別:

import ScreensUserForm from './screens/User/UserForm';
// vs
import ScreensUserForm from './screens/User/Form';

在上面的例子中,咱們看不出來明顯的優點。可是應用複雜度上升一點時就可以看到區別了。咱們來看看下面這個我實際項目中的例子:

import MediaPlanViewChannel from '/MediaPlan/MediaPlanView/MediaPlanViewChannel.jsx';
// vs
import MediaPlanViewChannel from './MediaPlan/View/Channel';

如今想象一下一個文件名中重複五到十次。

出於這樣的緣由,咱們認爲根據組件文件的上下文環境以及它的相對路徑來命名是更好的方式。

頁面(Screen)

若是要對一個用戶作增刪改查的操做,咱們須要有用戶列表頁面,建立新用戶的頁面以及編輯已有用戶的頁面。

在應用中,經過使用組件相互組合的結果,就是一個頁面。理想狀態下,頁面應該不包含任何邏輯,而僅僅是一個函數式組件。

咱們以 src 目錄爲根目錄,將不一樣頁面分散在不一樣文件夾中。由於它們是根據路由定義而不是模塊來劃分紅組的。

src
├─ components 
└─ screens
  └─ User
    ├─ Form.jsx
    └─ List.jsx

假設咱們項目中在使用 react-router,咱們在 screens 目錄下放置 Root.jsx 文件,而且在其中定義咱們應用全部的路由。

Root.jsx 的代碼可能像下面這樣:

import React, { Component } from 'react';
import { Router } from 'react-router';
import { Redirect, Route, Switch } from 'react-router-dom';

import ScreensUserForm from './User/Form';
import ScreensUserList from './User/List';

const ScreensRoot = () => (
  <Router>
    <Switch>
      <Route path="/user/list" component={ScreensUserList} />
      <Route path="/user/create" component={ScreensUserForm} />
      <Route path="/user/:id" component={ScreensUserForm} />
    </Switch>
  </Router>
);

export default ScreensRoot;

注意咱們將全部頁面都放在同一個目錄下,這個目錄以路由名稱命名。嘗試爲每一個父級路由創建一個目錄,在這個目錄中組織子路由。在這個示例中,咱們建立了 User 目錄而且將 List 頁面和 Form 頁面放在裏面。這種方式使你看一眼 url 就可以輕鬆定位當前路由渲染的頁面。

像上面的例子中的建立和編輯一個用戶的路由同樣,一個頁面可能會被兩個不一樣的路由渲染使用。

你可能注意到了全部的組件都包含 Screen 做爲名稱的前綴。當組件在組件目錄外使用時,咱們須要使用它們相對於 src 目錄的路徑來命名。位於 src/screens/User/List.jsx 的組件應該被命名爲 ScreensUserList。

包括 Root.jsx 在內,咱們的目錄結構以下:

src
├─ components 
└─ screens
  ├─ User
  │ ├─ Form.jsx
  │ └─ List.jsx
  └─ Root.jsx

別忘了在 index.js 中引入做爲應用根組件的 Root.jsx 。

若是你對一個頁面長什麼樣子還有疑問,看看下面的示例,它就是用戶表單的頁面。

import React from 'react';
import UserForm from '../../components/User/Form/Form';

const ScreensUserForm = ({ match: { params } }) => (
  <div>
    <h1>
      {`${!params.id ? 'Create' : 'Update'}`} User
    </h1>
    <UserForm id={params.id} />
  </div>
);

export default ScreensUserForm;

最終,咱們應用的目錄結構會像下面這樣:

src
├─ components 
│  ├─ User
│  │ ├─ Form
│  │ │ ├─ Form.jsx
│  │ │ └─ Form.css
│  │ └─ List.jsx
│  └─ UI 
│
└─ screens
  ├─ User
  │ ├─ Form.jsx
  │ └─ List.jsx
  └─ Root.jsx

回顧要點

  • 展現型和容器組件放在 src/components 目錄下
  • 經過模塊/特性(module/feature)的方式組織組件
  • 基礎的 UI 組件放在 src/components/UI 目錄下
  • 保持頁面簡單,使用最簡潔的結構和代碼
  • 經過路由定義組織頁面。對於 /user/list 路由地址來講,咱們會有一個頁面在 /src/screens/User/List.jsx。
  • 組件由相對 components 或 src 的路徑命名,就是說,處於 src/components/User/List.jsx 位置的組件會被命名爲 UserList。處於 src/screens/User/List.jsx 位置的組件會被命名爲 ScreensUserList
  • 組件和目錄同名時,不要在使用組件的時候重複這個名字。考慮這樣一個場景,處於 src/components/User/List/List.jsx 位置的組件會被命名爲 UserList 而不是 UserListList。

原文連接

相關文章
相關標籤/搜索