如何編寫Cleaner React代碼
大家好,我是小弋。
今天分享的內容是如何編寫干凈的React代碼。
正文
作為React開發者,我們都希望寫出更干凈的代碼,更簡單,更容易閱讀。
在本指南中,我把一些編寫更干凈的React代碼的七個頂級方法放在一起,使得構建React項目和審查你的代碼更容易。
總的來說,學習如何編寫更干凈的React代碼將使你成為一個更有價值的、整體上更快樂的React開發者,所以讓我們馬上開始吧!
主要涉及以下幾個點:
- 合理使用jsx。
- 把不相關的代碼移到一個獨立的組件中。
- 為每個組件創建單獨的文件。
- 將共享功能移入React hooks。
- 盡可能多地從你的JSX中刪除JavaScript。
- 格式化內聯樣式,減少臃腫的代碼。
- 合理使用React context。
合理使用JSX
你如何向一個給定的prop傳遞一個true的值?
在下面的例子中,我們使用showTitle在Navbar組件中顯示我們應用程序的標題。
- // src/App.js
- export default function App() {
- return (
- <main>
- <Navbar showTitle={true} />
- </main>
- );
- }
- function Navbar({ showTitle }) {
- return (
- <div>
- {showTitle && <h1>My Special App</h1>}
- </div>
- )
- }
我們是否需要明確地將showTitle設置為布爾值true?我們不需要!
一個快速的速記方法是,在一個組件上提供的任何prop的默認值都是true。
因此,如果我們在Navbar上添加showTitle,我們的標題元素就會顯示出來。
- // src/App.js
- export default function App() {
- return (
- <main>
- <Navbar showTitle />
- </main>
- );
- }
- function Navbar({ showTitle }) {
- return (
- <div>
- {showTitle && <h1>My Special App</h1>} // title shown!
- </div>
- )
- }
另一個要記住的有用的速記法涉及到傳遞字符串prop。
當你傳遞一個字符串的值時,你不需要用大括號把它包起來。
如果我們要設置導航條的標題,使用title的prop時,我們可以把它的值放在雙引號中。
- // src/App.js
- export default function App() {
- return (
- <main>
- <Navbar title="My Special App" />
- </main>
- );
- }
- function Navbar({ title }) {
- return (
- <div>
- <h1>{title}</h1>
- </div>
- )
- }
把不相關的代碼移到一個獨立的組件中
可以說,編寫更干凈的React代碼的最簡單和最重要的方法是善于將我們的代碼抽象成獨立的React組件。
讓我們看一下下面的例子:
- // src/App.js
- export default function App() {
- const posts = [
- {
- id: 1,
- title: "How to Build YouTube with React"
- },
- {
- id: 2,
- title: "How to Write Your First React Hook"
- }
- ];
- return (
- <main>
- <Navbar title="My Special App" />
- <ul>
- {posts.map(post => (
- <li key={post.id}>
- {post.title}
- </li>
- ))}
- </ul>
- </main>
- );
- }
- function Navbar({ title }) {
- return (
- <div>
- <h1>{title}</h1>
- </div>
- );
- }
我們的應用程序正在顯示一個導航條組件。我們正在用.map()遍歷一個帖子數組,并在頁面上顯示其標題。
我們思考一個問題,我們怎樣才能使它更干凈呢?
我們為什么不把我們正在循環的代碼,抽象化,并在一個單獨的組件中顯示它們,我們稱之為FeaturePosts。
讓我們看看改進后的結果:
- // src/App.js
- export default function App() {
- return (
- <main>
- <Navbar title="My Special App" />
- <FeaturedPosts />
- </main>
- );
- }
- function Navbar({ title }) {
- return (
- <div>
- <h1>{title}</h1>
- </div>
- );
- }
- function FeaturedPosts() {
- const posts = [
- {
- id: 1,
- title: "How to Build YouTube with React"
- },
- {
- id: 2,
- title: "How to Write Your First React Hook"
- }
- ];
- return (
- <ul>
- {posts.map((post) => (
- <li key={post.id}>{post.title}</li>
- ))}
- </ul>
- );
- }
正如你所看到的,我們現在可以只看我們的App組件。
通過閱讀其中的組件名稱,即Navbar和FeaturePosts,我們可以準確地看到我們的應用程序所顯示的內容。
為每個組件創建單獨的文件
從我們之前的例子來看,我們把所有的組件都放在一個文件里,即app.js文件。
類似于我們將代碼抽象成獨立的組件以使我們的應用程序更具可讀性,為了使我們的應用程序文件更具可讀性,我們可以將我們擁有的每個組件放在一個單獨的文件中。
這又一次幫助我們在應用程序中分離關注點。這意味著每個文件只負責一個組件,如果我們想在我們的應用程序中重復使用一個組件,就不會混淆它的來源了。
- // src/App.js
- import Navbar from './components/Navbar.js';
- import FeaturedPosts from './components/FeaturedPosts.js';
- export default function App() {
- return (
- <main>
- <Navbar title="My Special App" />
- <FeaturedPosts />
- </main>
- );
- }
我們來看看Navbar中的代碼:
- // src/components/Navbar.js
- export default function Navbar({ title }) {
- return (
- <div>
- <h1>{title}</h1>
- </div>
- );
- }
接著我們看看FeaturedPosts中代碼:
- // src/components/FeaturedPosts.js
- export default function FeaturedPosts() {
- const posts = [
- {
- id: 1,
- title: "How to Build YouTube with React"
- },
- {
- id: 2,
- title: "How to Write Your First React Hook"
- }
- ];
- return (
- <ul>
- {posts.map((post) => (
- <li key={post.id}>{post.title}</li>
- ))}
- </ul>
- );
- }
此外,通過將每個單獨的組件包含在自己的文件中,我們可以避免一個文件變得過于臃腫。如果我們想把所有的組件都加入到app.js文件中,我們很容易看到我們的app.js文件變得非常大。
將共享功能移入React hooks
看看我們的FeaturePosts組件,假設我們不是顯示靜態的帖子數據,而是想從一個API中獲取我們的帖子數據。
我們可以用fetch API來做。你可以看到下面這個結果:
- // src/components/FeaturedPosts.js
- import React from 'react';
- export default function FeaturedPosts() {
- const [posts, setPosts] = React.useState([]);
- React.useEffect(() => {
- fetch('https://jsonplaceholder.typicode.com/posts')
- .then(res => res.json())
- .then(data => setPosts(data));
- }, []);
- return (
- <ul>
- {posts.map((post) => (
- <li key={post.id}>{post.title}</li>
- ))}
- </ul>
- );
- }
然而,如果我們想在多個組件中執行這一數據請求,該怎么辦?
比方說,除了FeaturePosts組件外,我們還想創建一個具有相同數據的Post組件。我們將不得不復制我們用來獲取數據的邏輯,并將其粘貼到該組件中。
為了避免這樣做,我們為什么不使用一個新的React鉤子,我們可以稱之為useFetchPosts:
- // src/hooks/useFetchPosts.js
- import React from 'react';
- export default function useFetchPosts() {
- const [posts, setPosts] = React.useState([]);
- React.useEffect(() => {
- fetch('https://jsonplaceholder.typicode.com/posts')
- .then(res => res.json())
- .then(data => setPosts(data));
- }, []);
- return posts;
- }
一旦我們在一個專門的 "鉤子 "文件夾中創建了這個鉤子,我們就可以在任何我們喜歡的組件中重復使用它,包括我們的FeaturePosts組件:
- // src/components/FeaturedPosts.js
- import useFetchPosts from '../hooks/useFetchPosts.js';
- export default function FeaturedPosts() {
- const posts = useFetchPosts()
- return (
- <ul>
- {posts.map((post) => (
- <li key={post.id}>{post.title}</li>
- ))}
- </ul>
- );
- }
盡可能多地從你的JSX中刪除JavaScript
另一個非常有用的,但經常被忽視的清理組件的方法是盡可能多地從我們的JSX中刪除JavaScript。
讓我們看一下下面的例子:
- // src/components/FeaturedPosts.js
- import useFetchPosts from '../hooks/useFetchPosts.js';
- export default function FeaturedPosts() {
- const posts = useFetchPosts()
- return (
- <ul>
- {posts.map((post) => (
- <li onClick={event => {
- console.log(event.target, 'clicked!');
- }} key={post.id}>{post.title}</li>
- ))}
- </ul>
- );
- }
我們正試圖處理一個帖子的點擊事件。你可以看到,我們的JSX變得更加難以閱讀。鑒于我們的函數是作為一個內聯函數包含的,它掩蓋了這個組件的目的,以及它的相關函數。
我們能做什么來解決這個問題呢?我們可以把與onClick相連的內聯函數提取出來,變成一個單獨的處理程序,我們可以給它一個合適的名字,如handlePostClick。
一旦我們這樣做,我們的JSX就會再次變得可讀。
- // src/components/FeaturedPosts.js
- import useFetchPosts from '../hooks/useFetchPosts.js';
- export default function FeaturedPosts() {
- const posts = useFetchPosts()
- function handlePostClick(event) {
- console.log(event.target, 'clicked!');
- }
- return (
- <ul>
- {posts.map((post) => (
- <li onClick={handlePostClick} key={post.id}>{post.title}</li>
- ))}
- </ul>
- );
- }
格式化內聯樣式,減少臃腫的代碼
React開發者經常會在他們的JSX中寫內聯樣式。
但是,這使我們的代碼更難閱讀,更難寫出額外的JSX。
- // src/App.js
- export default function App() {
- return (
- <main style={{ textAlign: 'center' }}>
- <Navbar title="My Special App" />
- </main>
- );
- }
- function Navbar({ title }) {
- return (
- <div style={{ marginTop: '20px' }}>
- <h1 style={{ fontWeight: 'bold' }}>{title}</h1>
- </div>
- )
- }
我們想把這種關注點分離的概念應用到我們的JSX樣式中,把我們的內聯樣式移到一個CSS樣式表中,我們可以把它導入我們喜歡的任何組件。
另一種重寫內聯樣式的方法是將它們組織成對象。你可以看到這種模式是什么樣子的:
- // src/App.js
- export default function App() {
- const styles = {
- main: { textAlign: "center" }
- };
- return (
- <main style={styles.main}>
- <Navbar title="My Special App" />
- </main>
- );
- }
- function Navbar({ title }) {
- const styles = {
- div: { marginTop: "20px" },
- h1: { fontWeight: "bold" }
- };
- return (
- <div style={styles.div}>
- <h1 style={styles.h1}>{title}</h1>
- </div>
- );
- }
合理使用React context
在你的React項目中,另一個必不可少的模式是使用React Context(特別是如果你有共同的屬性,你想在你的組件中重復使用,而你發現自己寫了很多重復的props)。
例如,如果我們想在多個組件之間共享用戶數據,而不是多個重復的props(一種叫做props drilling的模式),我們可以使用React庫中的context功能。
在我們的例子中,如果我們想在我們的Navbar和FeaturePosts組件中重復使用用戶數據,我們所需要做的就是把我們的整個應用包裹在一個提供者組件中。
接下來,我們可以在值prop上傳遞用戶數據,并在useContext鉤子的幫助下,在我們的各個組件中消費該上下文。
- // src/App.js
- import React from "react";
- const UserContext = React.createContext();
- export default function App() {
- const user = { name: "Reed" };
- return (
- <UserContext.Provider value={user}>
- <main>
- <Navbar title="My Special App" />
- <FeaturedPosts />
- </main>
- </UserContext.Provider>
- );
- }
- // src/components/Navbar.js
- function Navbar({ title }) {
- const user = React.useContext(UserContext);
- return (
- <div>
- <h1>{title}</h1>
- {user && <a href="/logout">Logout</a>}
- </div>
- );
- }
- // src/components/FeaturedPosts.js
- function FeaturedPosts() {
- const posts = useFetchPosts();
- const user = React.useContext(UserContext);
- if (user) return null;
- return (
- <ul>
- {posts.map((post) => (
- <li key={post.id}>{post.title}</li>
- ))}
- </ul>
- );
- }
總結
我希望當你試圖改進你自己的React代碼,使其更干凈,更容易閱讀,并最終更愉快地創建你的React項目時,你會發現這個指南很有用。