在 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>
);
當你使用帶有箭頭函數的中括號時,你就新起了一個函數的作用域。箭頭函數的縮寫語法不用中括號。
代碼部署后可能存在的BUG沒法實時知道,事后為了解決這些BUG,花了大量的時間進行log 調試,這邊順便給大家推薦一個好用的BUG監控工具 Fundebug。
10. 不使用圓括號包裝對象
當你想要創建一個返回普通對象的箭頭函數時,上面的花括號和圓括號問題也會讓你感到困惑。
不要用:
const myAction = () => { type: 'DO_THIS' };
這樣用:
const myAction = () => ({ type: 'DO_THIS'});
如果沒有把對象包裹在圓括號里,你就不能使用縮寫語法。實際上你會給字符串定義一個標簽。
這在 setState
方法的 updater
函數中很常見,因為它需要返回一個對象。 如果要使用箭頭函數語法,則需要用括號包裝該對象。
不要用:
this.setState(prevState => { answer: 42 });
這樣用:
this.setState(prevState => ({ answer: 42 }));
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 />
如果放錯放了 / 字符的位置,將收到如下錯誤:
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、金山軟件、百姓網等眾多品牌企業。歡迎大家免費試用!