React 常見的 15 個問題
摘要:學習 React。
- 原文: React.js 常見問題
- 作者: 前端小智
Fundebug經授權轉載,版權歸原作者所有。
在 jsComplete ,我們管理一個專門用於幫助程式設計學習者 slack 帳戶 。我們常常會收到一些有趣的問題,但大多數問題都是常見問題。 我建立這個資源為了幫助 React.js 學習者遇到這些常見的問題時提供一定幫助。在這裡可以快速找到一些常見問題的解決方案,而不是一,遍又一遍去找解決方法,我會持續更新這些常見的問題。
1. 元件的名稱開頭要大寫
React 元件名稱必須具有以大寫字母開頭。
如果元件名稱不以大寫字母開頭,則元件使用將被視為內建元素,例如 div
或 span
。
例如:
class greeting extends React.Component { // ... }
如果嘗試渲染 <greeting />
,React 將忽略上述內容,會報以下警告:
Warning: The tag <greeting> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.
這裡更大的問題是把元件命名為 button
或 img
。 React 會忽略你的元件,只是渲染一個普通 HTML button
或 img
標籤。
注意上面沒有渲染 “My Awesome Button” 和 React 剛剛呈現的空 HTML 按鈕元素。 在這種情況下,React 不會警告你。
2. 使用單引號而不是反引號
用反引號 (
… )
建立的字串與用單引號 ('…')
建立的字串不同。
在大多數鍵盤上,可以使用 tab 鍵上方的鍵來輸入用反引號( ` )字元。
當需要在字串中包含動態表示式時,使用反引號建立一個字串(不需要使用字串連線)。
`This is a string template literal that can include expressions`; ("This is just a string, you cannot include expressions here");
假設你想要一個始終報告當前時間的字串:
“Time is …”
// Current time string const time = new Date().toLocaleTimeString(); // 使用普通字串(單引號或雙引號)時,需要使用字串連線: "Time is " + time`Time is ${ time // 在使用反引號時,可以使用 ${} 在字串中注入時間 }`;
此外,反引號還宣告一個字串時,可以建立一個跨多行的字串:
const template = `I CAN SPAN Multiple Lines`;
常規字串不能這樣做。
3. 使用 React.PropTypes
PropTypes
物件已從 React 中刪除。 其過去是以 React.PropTypes 的形式被使用,但不能再使用它了。
相應的,你需要:
npm install prop-types import PropTypes from 'prop-types'
然後就可以使用它啦,如: PropTypes.string
。
如果你錯誤地使用了 React.PropTypes
,會得到這樣的錯誤提示:
TypeError: Cannot read property 'string' of undefined
4. 沒有使用指南里指定的版本
在看或閱讀有關程式碼的內容以及使用指南里的例子時,確保你使用的庫版本與例子的裡的版本是一致的。一般使用最新版本是沒有問題,但是如果內容過時,你可能會遇到一些棄用問題。
為了安全起見,請使用主幹版本。 例如,如果教程裡使用的是 React 16,自己就不要使用 React 15 進行開發了。
這對 Node.js 也很重要。如果使用舊版本的 Node,會遇到一系列問題。 例如,如果你正在看一些教程,這些教程使用了 Object.values
,而你現在用 Node 6.x,那個版本此方法是不存在的。 你需要 Node 7.x 或更高版本。
5. 令人困惑的函式與類
你能看出下面的程式碼有什麼問題嗎?
class Numbers extends React.Component { const arrayOfNumbers = _.range(1, 10); // ... }
上面的程式碼是無效的,因為在 JavaScript 類的內部,不能隨意定義變數,只能使用規定的語法定義方法和屬性。
這有點令人困惑,因為類語法中使用的 {}
看起來像塊級作用域,但它並不是。
在一個由函式構成的元件裡,你就可以想怎麼搞就怎麼搞:
// Totally Okay: const Number = props => { const arrayOfNumbers = _.range(1, 10); // ... };
6. 將數字作為字串傳遞
你可以通過 prop 屬性傳遞一個字串:
<Greeting name="World" />
如果需要傳遞一個數值,不要使用字串:
// 不要這樣做 <Greeting counter="7" />
相反,使用花括號傳遞一個實際的數值:
<Greeting counter="{7}" />
在 Greeting 元件中使用 {7}
, this.props.counter
就被賦值為數字 7
,並且可以對其進行數學運算。 如果將其作為“7”傳遞,然後將其視為數字,則可能會遇到意外結果。
7. 忘記了另外一個 app 在用同樣的埠
要執行 web 伺服器,需要使用主機(如 127.0.0.1 )和埠(如 8080)使伺服器偵聽有效 http 地址上的請求。
一旦成功執行,web 伺服器就佔據了那個埠,你就不能讓這個埠它用,埠會被佔用。
如果你嘗試在另一個終端上執行相同的伺服器,將會得到埠被佔用的錯誤提示,如下:
Error: listen EADDRINUSE 127.0.0.1:8080
請注意,有時 Web 伺服器可能在後臺執行或在分離的螢幕/tmux 會話中執行。你看不到,但它仍然佔據了埠。 要重新啟動伺服器,需要“殺死”仍在執行的伺服器。
要識別使用某個埠的程序,可以使用 ps
之類的命令(以及關於應用程式的 grep),或者如果你知道埠號,則可以使用 lsof
命令:
lsof -i :8080
8. 忘記建立環境變數
有些專案依賴於 shell 環境變數的存在才能啟動。 如果在沒有所需環境變數的情況下執行這些專案,它們將嘗試為它們使用未定義的值,並可能會給你一些神祕的錯誤。
例如,如果專案連線到像 MongoDB 這樣的資料庫,則可能會使用像 process.env.MONGO_URI
這樣的環境變數來連線它。 這允許專案與不同環境中的不同 MongoDB 例項一起使用。
要在本地執行連線到 MongoDB 的專案,首先必須匯出 MONGO_URI
環境變數。 例如,如果你在埠 27017
上執行本地 MongoDB,則需要在執行專案之前執行此操作:
export MONGO_URI="mongodb://localhost:27017/mydb"
你可以 grep 專案原始碼,找到 process.env
來查清楚其執行正常所需要的環境變數。
9. 把花括號{}和圓括號()搞混
不要用:
return { something(); };
這樣用:
return ( something(); );
第一個將嘗試(並且會失敗)返回一個物件,而第二個將正確呼叫 something()
函式並返回該函式返回的內容。
因為 JSX 中的任何 <tag>
都將轉換為函式呼叫,所以在返回任何 JSX 時都會出現這個問題。
這個問題在箭頭函式的縮寫語法中也很常見。
不要用:
const Greeting = () => { <div>Hello World</div>; };
這樣用:
const Greeting = () => <div>Hello World</div>;
當你使用帶有箭頭函式的中括號時,你就新起了一個函式的作用域。箭頭函式的縮寫語法不用中括號。
10. 不使用圓括號包裝物件
當你想要建立一個返回普通物件的箭頭函式時,上面的花括號和圓括號問題也會讓你感到困惑。
不要用:
const myAction = () => { type: "DO_THIS"; };
這樣用:
const myAction = () => ({ type: "DO_THIS" });
如果沒有把物件包裹在圓括號裡,你就不能使用縮寫語法。實際上你會給字串定義一個標籤。
這在 setState
方法的 updater
函式中很常見,因為它需要返回一個物件。 如果要使用箭頭函式語法,則需要用括號包裝該物件。
不要用:
this.setState(prevState => { answer: 42; });
這樣用:
this.setState(prevState => ({ answer: 42 }));
程式碼部署後可能存在的 BUG 沒法實時知道,事後為了解決這些 BUG,花了大量的時間進行 log 除錯,這邊順便給大家推薦一個好用的 BUG 監控工具Fundebug。
11. 沒有正確使用 API 元素和屬性的大小寫
使用 React.Component
,而不是 React.component
。 使用 componentDidMount
,而不是 ComponentDidMount
。使用 ReactDOM
,而不是 ReactDom
。
請注意需要的 API 大小寫。 如果使用不正確的大小寫,得到的錯誤資訊可能不會很明確。
從 react
和 react-dom
匯入時,確保引入正確的名稱,並且使用的內容與引入完全相同,ESLint 可以幫助你指出未使用的內容。
在處理元件屬性時也會遇到這種問題:
<Greeting userName="Max" /> // 在元件內部,你需要使用 props.userName 來獲取傳入的值
如果你沒有使用 props.userName
,而是 props.username
或 props.UserName
,你會相當於用了一個 undefined
的值。需要特別留意下這點,當然更好的是配置 ESLint ,它也能指出這些問題。
12. 將 state 物件與例項屬性搞混
在類元件中,可以定義本地 state
物件,然後使用 this
訪問它:
class Greeting extends React.Component { state = { name: "World" }; render() { return `Hello ${this.state.name}`; } }
以上程式碼會輸出 “Hello World”
。
除 state
之外,你還可以定義其它本地例項屬性。
class Greeting extends React.Component { user = { name: "World" }; render() { return `Hello ${this.user.name}`; } }
以上程式碼也會輸出 “Hello World”
。
state
例項屬性是一個特殊屬性,因為 React 會管理它。 你只能通過 setState
更改它,當你這樣做時 React 會做出響應。
然而,定義的所有其他例項屬性對渲染演算法沒有影響。 你可以根據需要在上面的示例中更改 this.user
,並且 React 不會在 React 中觸發渲染機制。
13. 將
與</ tag> 搞混
在閉合標籤裡放錯 /
字元。不可否認,有時你可以使用 <tag/>
,而其他時間你需要 </tag>
。
在 HTML 中,有一種稱為“自閉合標籤”(AKA void tag)。這些是表示沒有任何子節點的元素的標記。例如,img 標籤是一個自閉合標籤:
<img src="..." /> // 不必使用 <img></img>
div 標籤可以包含子項,因此你可以使用開始和結束標記:
<div> Children here... </div>
這同樣適用於 Reac t 元件,如果元件具有子元素,如下所示:
<Greeting>Hello!</Greeting> // 注意/字元的位置
如果元件沒有子元素,可以用開始/結束標籤書寫,或者用自閉合標籤:
// 兩種方式 <Greeting></Greeting> <Greeting />
以下方式是非法的:
// 錯誤 <Greeting><Greeting /> </Greeting>
如果放錯放了 / 字元的位置,將收到如下錯誤:
Syntax error: Unterminated JSX contents
14. 假設 import/export 起作用
import/export
特性是 JavaScript 中的官方功能(自 2015 年起)。 其只是 ES2015 的特性,並且還沒有在流行瀏覽器和最新版 Node 裡面被完整支援。
React 專案的流行配置使用 Webpack 和 Babel。 兩者都允許使用此特性並將其編譯為所有瀏覽器都能理解的內容。 只有工作流中有 Webpack 或 Babel 之類的轉譯工具,才能使用 import/export
。
但是,在 React 打包應用程式中 import/export 不意味著可以隨意使用它們! 例如,如果你還通過最新的 Node 進行伺服器端渲染,會行不通,你很可能會收到 “unexpected token”
錯誤。
要讓 Node 理解 import/export
(如果你在前端使用它們就是你需要了解它們,並且也想要做 SSR 渲染的話),你需要有可以編譯其的 Babel preset(像是 env preset),才能在 Node 端執行。 你可以在開發時使用像 pm2\ 、 _nodemon_ 和 babel-watch 的工具做到這點,,並在每次更改內容時重新啟動 Node。
15. 不繫結處理程式方法
我把這個留到最後,因為這是一個大問題,一個很常見的問題。
你可以在 React 元件中定義類方法,然後在元件的 render 方法中使用它們。 例如:
class Greeting extends React.Component { whoIsThis() { console.dir(this); // "this" is the caller of whoIsThis return "World"; } render() { return `Hello ${this.whoIsThis()}`; } } ReactDOM.render(<Greeting />, mountNode);
我在 render
裡以 this.whoIsThis
的方式呼叫 whoIsThis
方法,因為在 render
中, this
關鍵字指的是與表示元件的 DOM 元素關聯的元件例項。
內部 React 確保其類方法中的 “this”
指向例項。 但是,當你使用 whoIsThis
方法的引用時,JavaScript 不會自動繫結例項。
whoIsThis
方法中的 console.dir
可以正確告訴我們當前元件例項,因為該方法是使用顯式呼叫方( this
)直接從 render
方法呼叫的。 執行上面的程式碼時,在控制檯中看到 Greeting 物件:
然而,當在延遲執行通道,例如事件處理裡執行同樣方法時,呼叫物件將不再是顯性的, console.dir
也不會列印當前元件例項。
在上面的程式碼中,當單擊字串時,React 會呼叫 whoIsThis
方法,但它不會讓你訪問元件例項。 這就是我們點選字串時得到 undefined
的原因。 如果類方法需要訪問像 this.props
和 this.state
這樣的屬性,這會是一個問題,因為它根本行不通。
對於這個問題有很多解決方案。可以將方法包裝在行內函數中,或使用 .bind
來改變 this
指向。 對於不經常更新的元件,兩者都可以。
還可以通過在類的建構函式中而不是在 render
方法中執行來優化 bind
方法。 但是,此方法的最佳解決方案是通過 Babel 來使用 ECMAScript 類欄位我(目前還是 stage-3),這樣對於處理程式只需使用箭頭函式就可以了:
class Greeting extends React.Component { whoIsThis = () => { console.dir(this); }; render() { return <div onClick={this.whoIsThis}>Hello World</div>; } }
這樣會和預期一樣執行:
原文: React.js Commonly Faced Problems
關於Fundebug
Fundebug專注於JavaScript、微信小程式、微信小遊戲、支付寶小程式、React Native、Node.js和Java線上應用實時BUG監控。 自從2016年雙十一正式上線,Fundebug累計處理了10億+錯誤事件,付費客戶有Google、360、金山軟體、百姓網等眾多品牌企業。歡迎大家免費試用!