React
簡介由於以前作了挺久的Vue
開發。如今開始學習React
也是並不難的。首先要對React
的總體脈絡要有個大概的瞭解。(注:這裏只作學習的記錄和總結,有些可能會遺漏,後期有了新的感悟也會慢慢完善,一些基礎的語法的使用仍是要看官方網站爲主)。javascript
從上圖能夠得知React
分爲三大致系:css
React.js
ReactNative
ReactVR
學習的順序也是React.js
->ReactNative
->ReactVR
按部就班的。html
React
的基礎大致包括下面這些概念:JSX
Virtual DOM
Data Flow
React.js
不是一個框架,它只是一個庫。它只提供UI
(view)層面的解決方案。在實際的項目當中,它並不能解決咱們全部的問題,須要結合其它的庫,例如Redux
、React-router
等來協助提供完整的解決方法。前端
React
開發環境的搭建React
<!DOCTYPE html>
<html> <head> <meta charset="UTF-8"> <!--https://cdnjs.com/--> <script src="./js/react.development.js"></script> <script src="./js/react-dom.development.js"></script> <script src="./js/browser.min.js"></script> <script src="./js/prop-types.min.js"></script> </head> <body> <div id="root"></div> <!--凡是使用 JSX 的地方,都要加上 type="text/babel"--> <script type="text/babel"> class Hello extends React.Component { render() { return (<h1>Hello,{this.props.name}</h1>) } } ReactDOM.render( <Hello name="zhangsan" />, document.getElementById('root') ); </script> </body> </html> 複製代碼
上面代碼一共用了四個庫:react.js
、react-dom.js
、browser.js
和prop-types.min.js
,它們必須首先加載。vue
react.js
:
React
的核心庫。
react-dom.js
:負責
Web
頁面的
DOM
操做。
browser.js
:將
JSX
語法轉爲
JavaScript
語法。
prop-types.min.js
:是傳入的
props
類型的驗證。自
React v15.5
起,
React.PropTypes
已移入另外一個包中,使用
prop-types
庫代替。
須要注意的是從
Babel 6.0
開始,再也不直接提供瀏覽器直接編譯的JS
版本,而是要用構建工具構建出來。這裏只作演示用。建議學習開發的時候也不要使用瀏覽器直接引入的方式。java
node.js
npm
create-react-app
,相似於
Vue
的
vue-cli
npm install -g create-react-app
// 在任意文件夾中使用create-react-app新建項目 create-react-app demo 複製代碼
安裝成功以後,
npm start
或者yarn start
就能夠啓動項目了。node
create-react-app
生成的目錄結構以下:react
demo/ README.md package.json yarn.lock .gitignore node_modules/ public/ favicon.ico index.html ... src/ App.css App.js App.test.js index.css index.js logo.svg serviceWorker.js 複製代碼
package.json
安裝的React
依賴介紹以下:webpack
從package.json
的dependencies
能夠看出來腳手架工具默認安裝了React
須要的依賴。下面就介紹這些核心依賴的做用:git
react
:是
React
的核心庫。
react-dom
:負責
Web
頁面的
DOM
操做。
react-scripts
:生成項目全部的依賴。例如
babel
,
css-loader
,
webpack
等從開發到打包前端工程化所須要的
react-scripts
都幫咱們作好了。
如今就能夠在App.js
裏編寫咱們本身的代碼了,以下(已修改生成的源碼):
在項目根目錄執行npm start
(若安裝了yarn
可以使用yarn start
),打開http://localhost:3000
就能夠看到首頁了:
建議你們參考Create React App 中文文檔
要想理解JSX
的由來,就要先介紹一下Virtual DOM
。
一個真實頁面對應一個DOM
樹。在傳統頁面的開發模式中,每次須要更新頁面時,都要手動操做DOM
來進行更新。DOM
操做很是昂貴。並且這些操做DOM
的代碼變得難以維護。
React
把真實DOM
樹轉換成JavaScript
對象樹,也就是Virtual DOM
。以下圖:
每次數據更新後,從新計算Virtual DOM
,並和上一次生成的Virtual DOM
作對比,對發生 變化的部分作批量更新。VirtualDOM
不只提高了React
的性能,並且它最大的好處其實還在於方便和其餘平臺集成(好比react-native
是基於Virtual DOM
渲染出的原生控件)。
所以在
Virtual DOM
輸出的時候,是輸出Web DOM
,仍是Android
控件,仍是iOS
控件,由平臺自己決定。
Web
頁面是由一個個HTML
元素嵌套組合而成的。當使用JavaScript
來描述這些元素的時候,這些元素能夠簡單地被表示成純粹的JSON
對象。好比,咱們如今須要描述一個按鈕(button
),用HTML
語法表示很是簡單:
<button class="primary">
<em>submit!</em> </button> 複製代碼
其中包括了元素的類型和屬性。若是轉成JSON
對象,會包括元素的類型以及屬性:
{
type: 'button', props: { className: 'primary', children: [{ type: 'em', props: { children: 'submit' } }] } } 複製代碼
上面的JS
對象表達了一個按鈕功能。在表達還不怎麼複雜的結構時,書寫就已經很難受了。這讓咱們想起使用HTML
編寫結構時的簡潔。因此JSX
語法爲此應運而生。假如咱們使用JSX
語法來從新表達上述button
元素,只需下面這麼寫:
ReactDOM.render(
<button className="primary"> <em>submit!</em> </button> , document.getElementById('root')) 複製代碼
JSX
將HTML
語法直接加入到JavaScript
代碼中,會讓代碼更加直觀並易於維護。經過編譯器轉換到純JavaScript
後由瀏覽器執行。JSX
在產品打包階段都已經編譯成了純JavaScript
。
注意:
JSX
是JavaScript
語言的一種語法擴展,長得像HTML
,但並非HTML
。儘管JSX
是第三方標準,但這套標準適用於任何一套框架。如今已所有采用Babel
的JSX
編譯器來實現對JSX
語法的編譯。
class Test extends React.Component {
constructor(props) { super(props) } render() { return ( <div> <h1 className='title'>Hello,React</h1> </div> ) } } ReactDOM.render(<Test />, document.getElementById('root')) 複製代碼
上面的代碼通過編譯之後會變成以下:
class Test extends React.Component {
constructor(props) { super(props) } render() { return ( React.createElement( 'div', null, React.createElement( 'h1', { className: 'title' }, 'Hello,React' ) ) ) } } ReactDOM.render(<Test />, document.getElementById('root')) 複製代碼
React.createElement
會構建一個JavaScript
對象來描述HTML
結構的信息,包括標籤名、屬性、還有子元素等。這樣的代碼就是合法的JavaScript
代碼了。
因此從JSX
到頁面通過了以下圖的過程:
ReactDOM.render(<div className="foo">Hello</div>, document.getElementById('root'))
複製代碼
HTML
裏的class
在JSX
裏要寫成className
,由於class
在JS
裏是保留關鍵字。同理某些屬性好比for
要寫成htmlFor
。
JavaScript
表達式JSX
遇到HTML
標籤(以<
開頭),就用HTML
規則解析。遇到代碼塊(以 {
開頭),就用 JavaScript
規則解析。
const names = ['zhangsan', 'lisi', 'wangwu']
ReactDOM.render( <div> { names.map((name,key) => { return <div key={key}>Hello,{name}</div> }) } </div>, document.getElementById('root') ) 複製代碼
JSX
容許直接在模板插入JavaScript
變量。若是這個變量是一個數組,則會展開這個數組的全部成員。
const names = [<div key="1">zhangsan</div>, <div key="2">lisi</div>] ReactDOM.render( <div> {names} </div>, document.getElementById('root') ) 複製代碼
在JSX
裏使用註釋也很簡單,就是沿用JavaScript
,惟一要注意的是在一個組件的子元素位置使用註釋要用 {}
包起來。
const App = (
<Nav> {/* 節點註釋 */} <Person /* 多行 註釋 */ name={window.isLoggedIn ? window.name : ''} /> </Nav> ) 複製代碼
HTML
轉義React
會將全部要顯示到DOM
的字符串轉義,防止XSS
。因此若是JSX
中含有轉義後的實體字符好比 ©
(©) 最後顯示到 DOM
中不會正確顯示,由於React
自動把©
中的特殊字符轉義了。可使用dangerouslySetInnerHTML
來實現。
ReactDOM.render(
<div dangerouslySetInnerHTML={{ __html: '© 2020' }} />, document.getElementById('root') ) 複製代碼
若是在JSX
中使用的屬性不存在於HTML
的規範中,這個屬性會被忽略。若是要使用自定義屬性,能夠用data-
前綴。可訪問性屬性的前綴aria-
也是支持的。
ReactDOM.render(<div data-attr="abc">content</div>, document.getElementById('root'))
複製代碼
React
認爲組件是和模板緊密關聯的,組件模板和組件邏輯分離讓問題複雜化了。React
容許將代碼封裝成組件(component
),而後像插入普通 HTML
標籤同樣,在網頁中插入這個組件。一個React
應用就是構建在React
組件之上的。Component
(組件)能夠是類組件(class component
)、函數式組件(function component
)。
組件有三個核心概念,React
組件基本上由組件的構建方式
、組件內的屬性狀態
與生命週期方法
組成。以下圖:
props
(屬性)
states
(狀態)
注意:組件生成的
HTML
結構只能有一個單一的根節點。在React
中,數據是自頂向下單向流動的,即從父組件到子組件。
官方在React
組件構建上提供了3
種不一樣的方法:React.createClass
、ES6 classes
和無狀態 函數(stateless function
)。
官方在
React@15.5.0
後不推薦用React.createClass
,建議使用ES6 class
,這裏不作具體介紹。
ES6 classes
class Test extends React.Component {
constructor(props) { super(props) } render() { return (<h1>Hello,React</h1>) } } 複製代碼
能夠用純函數來定義無狀態的組件(stateless function
),這種組件沒有狀態,沒有生命週期,只是簡單的接受props
渲染生成DOM
結構。無狀態組件很是簡單,開銷很低。好比使用箭頭函數定義:
const Hello = (props) => <div>Hello {props.name}</div>
ReactDOM.render(<Hello name="張三" />, document.getElementById('root')) 複製代碼
props
就是組件的屬性,由外部經過JSX
屬性傳入設置,一旦初始設置完成,就能夠認爲this.props
是不可更改的,因此不要輕易更改設置this.props
裏面的值。
class Hello extends React.Component {
constructor(props) { super(props) } render() { return ( <h1>Hello,{this.props.name}</h1> ) } } // PropTypes 驗證,若傳入的props type不是string將提示錯誤 Hello.propTypes = { name: PropTypes.string } // Prop 初始值,若是 prop 沒有傳入值將會使用 default 值 lisi Hello.defaultProps = { name: 'lisi' } ReactDOM.render(<Hello name="張三" />, document.getElementById('root')) 複製代碼
state
state
是組件的當前狀態,用this.state
來存取state
。一旦狀態(數據)更改,組件就會自動調用 render
從新渲染UI
,這個更改的動做會經過this.setState
方法來觸發。
下面代碼是一個1000
毫秒就會加一的累加器:
class Timer extends React.Component {
constructor(props) { super(props) // 須要自行綁定 this context this.tick = this.tick.bind(this) this.state = { count: 0 } } // 累加器方法,每一秒會使用 setState() 更新內部 state,讓 Component 從新 render tick() { this.setState({ count: this.state.count + 1 }) } // 生命週期函數 componentDidMount() { this.interval = setInterval(this.tick, 1000); } // 生命週期函數 componentWillUnmount() { clearInterval(this.interval); } render() { return ( <div>Count: {this.state.count}</div> ) } } ReactDOM.render(<Timer />, document.getElementById('root')) 複製代碼
每一個組件都包含都包含組件生命週期方法,在運行過程當中特定的階段執行這些方法。
組件的生命週期分爲四類,共有10個方法:
下面會依次介紹這幾類組件的生命週期所調用的函數。
當組件實例被建立並插入DOM
中時,其生命週期調用順序以下:
constructor()
static getDerivedStateFromProps()
:會在調用
render
方法以前調用,而且在初始掛載及後續更新時都會被調用。
render()
:是
class
組件中惟一必須實現的方法。
componentDidMount()
:在組件掛載後(插入
DOM
樹中)當即調用。
當組件的props
或state
發生變化時會觸發更新。組件更新的生命週期調用順序以下:
static getDerivedStateFromProps()
shouldComponentUpdate()
:當
props
或
state
發生變化時,
shouldComponentUpdate()
會在渲染執行以前被調用。
render()
getSnapshotBeforeUpdate()
:在最近一次渲染輸出(提交到
DOM
節點)以前調用。
componentDidUpdate()
:會在更新後會被當即調用。首次渲染不會執行此方法。
當組件從DOM
中移除時會調用以下方法:
componentWillUnmount()
:在組件卸載及銷燬以前直接調用。
當渲染過程,生命週期,或子組件的構造函數中拋出錯誤時,會調用以下方法:
static getDerivedStateFromError()
:今生命週期會在後代組件拋出錯誤後被調用。
componentDidCatch()
:今生命週期在後代組件拋出錯誤後被調用。
這裏只作簡單介紹,後面會單獨寫文章詳細記錄生命週期的使用方式。
React
裏面綁定事件的方式和在HTML
中綁定事件相似,使用駝峯式命名指定要綁定的onClick
屬性爲組件定義的一個方法。下面是一個數字累加的小例子:
class AddCount extends React.Component {
constructor(props) { super(props) this.state = { count: 0 } } handleClick(e) { this.setState({ count: this.state.count + 1 }); } render() { return ( <div> <p>Count: {this.state.count}</p> <button onClick={this.handleClick.bind(this)}>Click me</button> </div> ) } } ReactDOM.render(<AddCount />, document.getElementById('root')) 複製代碼
注意要顯式調用
bind(this)
將事件函數上下文綁定要組件實例上,這也是React
推崇的原則:沒有黑科技,儘可能使用顯式的容易理解的JavaScript
代碼。
DOM
操做有時候咱們避免不了要直接操做DOM
。React
也提供了幾種咱們能夠直接操做DOM
的方式。
findDOMNode
當組件加載到頁面上以後(mounted
),能夠經過react-dom
提供的findDOMNode()
方法拿到組件對應的DOM
元素。
class Test extends React.Component {
constructor(props) { super(props) } componentDidMount() { const el = ReactDOM.findDOMNode(this) // 123456 console.log(el.textContent) } render() { return ( <div>123456</div> ) } } ReactDOM.render(<Test />, document.getElementById('root')) 複製代碼
注意:
findDOMNode()
不能用在無狀態組件上。findDOMNode
僅在組件始終返回永不更改的單個DOM
節點時才起做用。在<React.StrictMode>
嚴格模式下,這個方法已經被官方棄用,因此在開發中不要使用這個方法。
Refs
另一種方式就是經過在要引用的DOM
元素上面設置一個ref
屬性指定一個名稱,而後經過 this.refs.name
來訪問對應的DOM
元素。
class Test extends React.Component {
constructor(props) { super(props) } componentDidMount() { this.refs.textInput.focus() } render() { return ( <div> <input ref="textInput" /> </div> ) } } ReactDOM.render(<Test />, document.getElementById('root')) 複製代碼
不要在 render
或者render
以前訪問refs
。不要濫用 refs
。好比只是用它來按照傳統的方式操做界面UI:找到 DOM -> 更新 DOM
。
https://zh-hans.reactjs.org/
http://huziketang.mangojuice.top/books/react/
https://www.bilibili.com/video/BV1g4411i7po?p=2
https://www.jianshu.com/p/c6040430b18d
https://www.zhihu.com/question/336664883/answer/790855896
https://book.douban.com/subject/26918038/
https://www.kancloud.cn/digest/babel/217110
http://www.ruanyifeng.com/blog/2015/03/react.html
https://kdchang.gitbooks.io/react101/content/
https://github.com/mocheng/react-and-redux/issues/99
https://juejin.im/entry/587de1b32f301e0057a28897
https://www.html.cn/create-react-app/docs/getting-started/
本文使用 mdnice 排版