首頁 >web前端 >js教程 >React:可重複使用、解耦和隔離

React:可重複使用、解耦和隔離

WBOY
WBOY原創
2024-08-19 17:15:031088瀏覽

在處理新頁面時,不要只是開始在頁面本身上編寫元件。開始個性化元件單元,以便每個元件都足夠獨立,且全域狀態的變更不會導致重新渲染。例如,假設您被賦予建立此頁面的任務。

React: Reusable, Decoupled, and Isolated

您可能會像我學習程式設計時一樣,將所有這些元件寫入一個檔案中。

// WRONG
export const LoginPage = () => {
  const [userId, setUserId] = useState('')
  const [password, setPassword] = useState('')

  return (
    <div>
      <div className="d-flex flex-row">
        <div id="marketing-container">
          <svg {...fbLogo} />
          <h2>Connect with friends and the world around you on Facebook</h2>
        </div>
        <div id="login-form">
          <div>
            <input id="user-id" value={userId} onChange={(e) => setUserId(e.target.value)} />
            <input id="password" value={password} onChange={(e) => setPassword(e.target.value)} />
            <button>Log in</button>
            <a href="/forgot-password">Forgot password</a>
            <br />
            <button>Create new account</button>
          </div>
          <div>
            <strong>Create a page</strong>
            <span>for a celebrity, brand or business.</span>
          </div>
        </div>
      </div>
    </div>
  )
}

export default LoginPage

但現實是,許多這些元素將來可能會在整個應用程式中重複使用,這意味著它們需要重新編寫或複製/貼上。當使用定義良好的設計時,很可能會像樂高積木一樣使用元素,登入頁面上顯示的徽標將與儀表板螢幕上的徽標相同,當然可能只是尺寸不同。與使用者 ID 輸入相同,從設計角度來看,它可能與使用者編輯頁面上的相同。

這讓我想到了下一點,元件應該從業務邏輯到表示邏輯解耦。我的意思是,與狀態通信的部分應該是它自己的組件,並且該組件只是將演示道具傳遞給演示組件。

// Presentational component
const UserIdInputComponent = ({ value, onChange }) =>
  <input value={value} onChange={onChange} />

// Logic component
export const UserIdInput = () => {
  const [value, setValue] = useState('')

  const handleChange = (e) => setValue(e.target.value)

  return <UserIdInputComponent value={value} onChange={handleChange} />
}

這使得故事書等工具能夠透過僅導出與狀態管理分離的表示元件來正常運作。必須在 Storybook 上整合邏輯繁重的元件(進行 api 呼叫、進行全域狀態變更)可能會很煩人。透過這種方法,您可以看到組件如何根據不同的 props 在視覺上變化。

返回主頁。你大概可以明白我的意思了。而不是將所有內容都寫在同一頁上。想想如何重複使用該元件,如何將其與狀態解耦以及如何將其隔離,以便它永遠不會重新渲染,除非與該元件相關的 props 發生變化。

export const LoginPage = () => (
  <div>
    <div className="d-flex flex-row">
      <FbMarketing />
      <LoginForm />
    </div>
  </div>
)

export default LoginPage

最好的情況是您開始編碼的方式。一旦你看到一切都按預期工作,再回來重構它會更加麻煩。我很內疚地想在螢幕上快速看到一些東西,平息焦慮並從頭開始構建正確的結構。

const FbLogo = () => (
  <svg {...fbLogoAttributes} />
)

const FbSlogan = () => (
  <h2>Connect with friends and the world around you on Facebook.</h2>
)

const FbMarketing = () => (
  <>
    <FbLogo />
    <FbSlogan />
  </>
)

這裡是所有的示範。這些可以進一步個性化為 FbLogoSmall、FbLogoMedium 等。

現在包含一些邏輯的部分,即登入表單。不確定是登入、登入還是登錄,但我們將繼續使用 Facebook 的術語「登入」。

提醒一下,每個元件都應該可重複使用、解耦和隔離。

可重複使用

先讓UserIdInput 可重複使用,然後將此方法複製到其他密碼輸入上:值得注意的是,生產等級輸入將包含其他屬性,例如測試id、根據props 變更的類別、aria 屬性、自動對焦等等更多其他道具/屬性取決於程式碼庫使用的工具。如果有人告訴您這比我在這裡寫的更複雜,請聽那個人的意見。

// UserIdInput.js
import { useContext, createContext } from "react";

export const UserIdContext = createContext();

const UserIdInput = () => {
  const { userId, setUserId } = useContext(UserIdContext);

  const handleChange = (e) => {
    setUserId(e.target.value);
  };

  return <UserIdInputComponent value={userId} onChange={handleChange} />;
};

例如,現在可以在使用者編輯表單上重複使用此輸入。這是密碼輸入的樣子:

// PasswordInput.js
import { useContext, createContext } from "react";

export const PasswordContext = createContext();

const PasswordInput = () => {
  const { password, setPassword } = useContext(PasswordContext);

  const handleChange = (e) => {
    setPassword(e.target.value);
  };

  return (
    <div>
      <PasswordInputComponent value={password} onChange={handleChange} />
    </div>
  );
};

解耦

解耦是指將其邏輯與其表示部分、「業務邏輯」與視覺部分解耦:在這裡我們可以看到props 的傳遞沒有中間的修改或新的函數定義,哎呀我甚至返回了jsx直接沒有return 關鍵字。再說一次,如果有人告訴你它比這更複雜,那是…標籤應該是它們自己的組件,輸入也是。

// UserIdInputComponent.js
const UserIdInputComponent = ({ value, onChange }) => (
  <div>
    <label>User Id:</label>
    <input type="text" value={value} onChange={onChange} required />
  </div>
);
// PasswordInputComponent.js
const PasswordInputComponent = ({ value, onChange }) => (
  <div>
    <label>Password:</label>
    <input type="password" value={value} onChange={onChange} required />
  </div>
);

隔離

我們已經透過建立上下文來處理隔離部分,現在每當我們更改任何輸入時,其他輸入都不會重新渲染。唯一將重新呈現的元素是正在變更的輸入和登入按鈕。這是一個很好的指標,表明你的 React 應用程式是否得到了適當的優化,過早的優化有時是好的。提升團隊技能。

const LoginButton = () => {
  const { userId } = useContext(UserIdContext);
  const { password } = useContext(PasswordContext);

  const onClick = (e) => {
    e.preventDefault();
    console.log("form submit", userId, password)
  };

  return <button onClick={onClick}>Log in</button>;
};

除了!這實際上沒有發生,我嘗試使用上下文來隔離更改,但是當涉及到共用userId 和密碼時,我必須使用redux,因為一旦我使用UserIdProvider 來包裝LoginButton,它就會使用新的userId 和密碼建立一個新狀態。這就是 redux 的樣子。

// LoginButton.js
import { useSelector } from "react-redux";

const LoginButton = () => {
  const { userId, password } = useSelector(state => state)

  const onClick = (e) => {
    e.preventDefault();
    console.log("form submit", userId, password);
  };

  return <button onClick={onClick}>Log in</button>;
};

export default LoginButton

可能應該之前輸入過,但這裡是 redux 儲存。

// store.js
import { createSlice, configureStore } from '@reduxjs/toolkit'

const login = createSlice({
  name: 'login',
  initialState: {
    userId: '',
    password: '',
  },
  reducers: {
    userId: (state, action) => {
      state.userId = action.payload
    },
    password: (state, action) => {
      state.password = action.payload
    }
  }
})

export const { userId: setUserId, password: setPassword } = login.actions

export const store = configureStore({
  reducer: login.reducer
})

用 redux 約會自己,但它可以很好地隔離更改,從而最大限度地減少重新渲染。通常我不會太相信那些不惜一切代價避免重新渲染的人,但這只是良好反應程式碼的一個很好的指示。

React: Reusable, Decoupled, and Isolated

Here are the updated files for the two inputs. Not a lot changed but pay attention to how easy it was for me to change only the business logic component. Changed the value selector, the handleChange function and that was it. This is one of the advantages of decoupling, it’s not that obvious with such a small component but a codebase that uses complex logic I can see how this approach can be beneficial.

// UserIdInput.js (revised final)
import { setUserId } from "./store";
import { useDispatch, useSelector } from "react-redux";

const UserIdInputComponent = ({ value, onChange }) => (
  <div>
    <label>User Id:</label>
    <input type="text" value={value} onChange={onChange} required />
  </div>
);

const UserIdInput = () => {
  const userId = useSelector(({ userId }) => userId)
  const dispatch = useDispatch()

  const handleChange = (e) => {
    dispatch(setUserId(e.target.value))
  };

  return <UserIdInputComponent value={userId} onChange={handleChange} />;
};
// PasswordInput.js (revised final)
import { useDispatch, useSelector } from "react-redux";
import { setPassword } from "./store";

const PasswordInputComponent = ({ value, onChange }) => (
  <>
    <label>Password:</label>
    <input type="password" value={value} onChange={onChange} required />
  </>
);

const PasswordInput = () => {
  const password = useSelector(({ password }) => password)
  const dispatch = useDispatch()

  const handleChange = e => {
    dispatch(setPassword(e.target.value))
  };

  return <PasswordInputComponent value={password} onChange={handleChange} />
};

The result should only highlight updates on the changed input and the login button itself like so:

React: Reusable, Decoupled, and Isolated

There’s a problem though, the labels are also updating. Let’s fix that really quick just to prove the point of over, but potentially necessary optimization. Up to your discretion.

// UserIdInput.js
import { setUserId } from "./store";
import { useDispatch, useSelector } from "react-redux";

const UserIdInputComponent = ({ value, onChange }) => (
  <input type="text" value={value} onChange={onChange} required />
);

const UserIdInput = () => {
  const userId = useSelector(({ userId }) => userId)
  const dispatch = useDispatch()

  const handleChange = (e) => {
    dispatch(setUserId(e.target.value))
  };

  return <UserIdInputComponent value={userId} onChange={handleChange} />;
};

// separated the label from the logic heavy component
export const UserIdInputWithLabel = () => (
  <div>
    <label>User id: </label>
    <UserIdInput />
  </div>
)

export default UserIdInputWithLabel

Here is the password input.

// PasswordInput.js
import { useDispatch, useSelector } from "react-redux";
import { setPassword } from "./store";

const PasswordInputComponent = ({ value, onChange }) => (
  <input type="password" value={value} onChange={onChange} required />
);

const PasswordInput = () => {
  const password = useSelector(({ password }) => password)
  const dispatch = useDispatch()

  const handleChange = e => {
    dispatch(setPassword(e.target.value))
  };

  return <PasswordInputComponent value={password} onChange={handleChange} />
};

// separated label from logic heavy component
const PasswordInputWithLabel = () => (
  <div>
    <label>Password: </label>
    <PasswordInput />
  </div>
)

export default PasswordInputWithLabel

This approach yields the following results:

React: Reusable, Decoupled, and Isolated

Fully optimized.

Available here: https://github.com/redpanda-bit/reusable-decoupled-isolated

Conclusion

There you have it, reusable, decoupled, and isolated react components. Very small example but hope that it gives you an idea of how production grade react applications look like. It may be disappointing for some to see all the work that goes into creating a good react component, but I’ll tell ya, once you are faced with a huge form that has complex elements and possibly some animation you will see positive gains on speed. The last thing you want is an input lagging behind in front of a 100 words per minute types.

References:

https://nextjs.org/

https://redux.js.org/

以上是React:可重複使用、解耦和隔離的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn