React學(xué)習(xí)筆記(一)

React的起源和發(fā)展

React 起源于 Facebook 的內(nèi)部項(xiàng)目,因?yàn)樵摴緦?duì)市場(chǎng)上所有 JavaScript MVC 框架,都不滿意,就決定自己寫一套,用來(lái)架設(shè)Instagram 的網(wǎng)站。做出來(lái)以后,發(fā)現(xiàn)這套東西很好用,就在2013年5月開(kāi)源了。

React與傳統(tǒng)MVC的關(guān)系

輕量級(jí)的視圖層庫(kù)!A JavaScript library for building user interfaces

React不是一個(gè)完整的MVC框架,最多可以認(rèn)為是MVC中的V(View),甚至React并不非常認(rèn)可MVC開(kāi)發(fā)模式;React 構(gòu)建頁(yè)面 UI 的庫(kù)??梢院?jiǎn)單地理解為,React 將將界面分成了各個(gè)獨(dú)立的小塊,每一個(gè)塊就是組件,這些組件之間可以組合、嵌套,就成了我們的頁(yè)面。

React高性能的體現(xiàn):虛擬DOM

React高性能的原理:

在Web開(kāi)發(fā)中我們總需要將變化的數(shù)據(jù)實(shí)時(shí)反應(yīng)到UI上,這時(shí)就需要對(duì)DOM進(jìn)行操作。而復(fù)雜或頻繁的DOM操作通常是性能瓶頸產(chǎn)生的原因(如何進(jìn)行高性能的復(fù)雜DOM操作通常是衡量一個(gè)前端開(kāi)發(fā)人員技能的重要指標(biāo))。

React為此引入了虛擬DOM(Virtual DOM)的機(jī)制:在瀏覽器端用Javascript實(shí)現(xiàn)了一套DOM API。基于React進(jìn)行開(kāi)發(fā)時(shí)所有的DOM構(gòu)造都是通過(guò)虛擬DOM進(jìn)行,每當(dāng)數(shù)據(jù)變化時(shí),React都會(huì)重新構(gòu)建整個(gè)DOM樹(shù),然后React將當(dāng)前整個(gè)DOM樹(shù)和上一次的DOM樹(shù)進(jìn)行對(duì)比,得到DOM結(jié)構(gòu)的區(qū)別,然后僅僅將需要變化的部分進(jìn)行實(shí)際的瀏覽器DOM更新。而且React能夠批處理虛擬DOM的刷新,在一個(gè)事件循環(huán)(Event Loop)內(nèi)的兩次數(shù)據(jù)變化會(huì)被合并,例如你連續(xù)的先將節(jié)點(diǎn)內(nèi)容從A-B,B-A,React會(huì)認(rèn)為A變成B,然后又從B變成A UI不發(fā)生任何變化,而如果通過(guò)手動(dòng)控制,這種邏輯通常是極其復(fù)雜的。

盡管每一次都需要構(gòu)造完整的虛擬DOM樹(shù),但是因?yàn)樘摂MDOM是內(nèi)存數(shù)據(jù),性能是極高的,部而對(duì)實(shí)際DOM進(jìn)行操作的僅僅是Diff分,因而能達(dá)到提高性能的目的。這樣,在保證性能的同時(shí),開(kāi)發(fā)者將不再需要關(guān)注某個(gè)數(shù)據(jù)的變化如何更新到一個(gè)或多個(gè)具體的DOM元素,而只需要關(guān)心在任意一個(gè)數(shù)據(jù)狀態(tài)下,整個(gè)界面是如何Render的。

React Fiber:

在react 16之后發(fā)布的一種react 核心算法,React Fiber是對(duì)核心算法的一次重新實(shí)現(xiàn)(官網(wǎng)說(shuō)法)。之前用的是diff算法。

在之前React中,更新過(guò)程是同步的,這可能會(huì)導(dǎo)致性能問(wèn)題。

當(dāng)React決定要加載或者更新組件樹(shù)時(shí),會(huì)做很多事,比如調(diào)用各個(gè)組件的生命周期函數(shù),計(jì)算和比對(duì)Virtual DOM,最后更新DOM樹(shù),這整個(gè)過(guò)程是同步進(jìn)行的,也就是說(shuō)只要一個(gè)加載或者更新過(guò)程開(kāi)始,中途不會(huì)中斷。因?yàn)镴avaScript單線程的特點(diǎn),如果組件樹(shù)很大的時(shí)候,每個(gè)同步任務(wù)耗時(shí)太長(zhǎng),就會(huì)出現(xiàn)卡頓。

React Fiber的方法其實(shí)很簡(jiǎn)單——分片。把一個(gè)耗時(shí)長(zhǎng)的任務(wù)分成很多小片,每一個(gè)小片的運(yùn)行時(shí)間很短,雖然總時(shí)間依然很長(zhǎng),但是在每個(gè)小片執(zhí)行完之后,都給其他任務(wù)一個(gè)執(zhí)行的機(jī)會(huì),這樣唯一的線程就不會(huì)被獨(dú)占,其他任務(wù)依然有運(yùn)行的機(jī)會(huì)。

React的特點(diǎn)和優(yōu)勢(shì)

1、虛擬DOM

我們以前操作dom的方式是通過(guò)document.getElementById()的方式,這樣的過(guò)程實(shí)際上是先去讀取html的dom結(jié)構(gòu),將結(jié)構(gòu)轉(zhuǎn)換成變量,再進(jìn)行操作

而reactjs定義了一套變量形式的dom模型,一切操作和換算直接在變量中,這樣減少了操作真實(shí)dom,性能真實(shí)相當(dāng)?shù)母撸椭髁鱉VC框架有本質(zhì)的區(qū)別,并不和dom打交道

2、組件系統(tǒng)

react最核心的思想是將頁(yè)面中任何一個(gè)區(qū)域或者元素都可以看做一個(gè)組件 component

那么什么是組件呢?

組件指的就是同時(shí)包含了html、css、js、image元素的聚合體

使用react開(kāi)發(fā)的核心就是將頁(yè)面拆分成若干個(gè)組件,并且react一個(gè)組件中同時(shí)耦合了css、js、image,這種模式整個(gè)顛覆了過(guò)去的傳統(tǒng)的方式

3、單向數(shù)據(jù)流

其實(shí)reactjs的核心內(nèi)容就是數(shù)據(jù)綁定,所謂數(shù)據(jù)綁定指的是只要將一些服務(wù)端的數(shù)據(jù)和前端頁(yè)面綁定好,開(kāi)發(fā)者只關(guān)注實(shí)現(xiàn)業(yè)務(wù)就行了

4、JSX 語(yǔ)法

在vue中,我們使用render函數(shù)來(lái)構(gòu)建組件的dom結(jié)構(gòu)性能較高,因?yàn)槭∪チ瞬檎液途幾g模板的過(guò)程,但是在render中利用createElement創(chuàng)建結(jié)構(gòu)的時(shí)候代碼可讀性較低,較為復(fù)雜,此時(shí)可以利用jsx語(yǔ)法來(lái)在render中創(chuàng)建dom,解決這個(gè)問(wèn)題,但是前提是需要使用工具來(lái)編譯jsx

編寫第一個(gè)react應(yīng)用程序

react開(kāi)發(fā)需要引入多個(gè)依賴文件:react.js、react-dom.js,分別又有開(kāi)發(fā)版本和生產(chǎn)版本,create-react-app里已經(jīng)幫我們把這些東西都安裝好了。把通過(guò)CRA創(chuàng)建的工程目錄下的src目錄清空,然后在里面重新創(chuàng)建一個(gè)index.js. 寫入以下代碼:

// 從 react 的包當(dāng)中引入了 React。只要你要寫 React.js 組件就必須引入React, 因?yàn)閞eact里有一種語(yǔ)法叫JSX,稍后會(huì)講到JSX,要寫JSX,就必須引入React
import React from 'react'
// ReactDOM 可以幫助我們把 React 組件渲染到頁(yè)面上去,沒(méi)有其它的作用了。它是從 react-dom 中引入的,而不是從 react 引入。
import ReactDOM from 'react-dom'
?
// ReactDOM里有一個(gè)render方法,功能就是把組件渲染并且構(gòu)造 DOM 樹(shù),然后插入到頁(yè)面上某個(gè)特定的元素上
ReactDOM.render(
// 這里就比較奇怪了,它并不是一個(gè)字符串,看起來(lái)像是純 HTML 代碼寫在 JavaScript 代碼里面。語(yǔ)法錯(cuò)誤嗎?這并不是合法的 JavaScript 代碼, “在 JavaScript 寫的標(biāo)簽的”語(yǔ)法叫 JSX- JavaScript XML。
  <h1>歡迎進(jìn)入React的世界</h1>,
// 渲染到哪里
  document.getElementById('root')
)

元素與組件

如果代碼多了之后,不可能一直在render方法里寫,所以就需要把里面的代碼提出來(lái),定義一個(gè)變量,像這樣:

import React from 'react'
import ReactDOM from 'react-dom'
// 這里感覺(jué)又不習(xí)慣了?這是在用JSX定義一下react元素
const app = <h1>歡迎進(jìn)入React的世界</h1>
ReactDOM.render(
  app,
  document.getElementById('root')
)

函數(shù)式組件

由于元素沒(méi)有辦法傳遞參數(shù),所以我們就需要把之前定義的變量改為一個(gè)方法,讓這個(gè)方法去return一個(gè)元素:

import React from 'react'
import ReactDOM from 'react-dom'
?
// 特別注意這里的寫法,如果要在JSX里寫js表達(dá)式(只能是表達(dá)式,不能流程控制),就需要加 {},包括注釋也是一樣,并且可以多層嵌套
const app = (props) => <h1>歡迎進(jìn)入{props.name}的世界</h1>
?
ReactDOM.render(
  app({
    name: 'react'
  }),
  document.getElementById('root')
)

這里我們定義的方法實(shí)際上也是react定義組件的第一種方式-定義函數(shù)式組件,這也是無(wú)狀態(tài)組件。但是這種寫法不符合react的jsx的風(fēng)格,更好的方式是使用以下方式進(jìn)行改造

import React from 'react'
import ReactDOM from 'react-dom'
?
const App = (props) => <h1>歡迎進(jìn)入{props.name}的世界</h1>
?
ReactDOM.render(
  // React組件的調(diào)用方式
  <App name="react" />,
  document.getElementById('root')
)

這樣一個(gè)完整的函數(shù)式組件就定義好了。但要注意!注意!注意!組件名必須大寫,否則報(bào)錯(cuò)。

class組件

ES6的加入讓JavaScript直接支持使用class來(lái)定義一個(gè)類,react的第二種創(chuàng)建組件的方式就是使用的類的繼承,ES6 class是目前官方推薦的使用方式,它使用了ES6標(biāo)準(zhǔn)語(yǔ)法來(lái)構(gòu)建,看以下代碼:

import React from 'react'
import ReactDOM from 'react-dom'
?
class App extends React.Component {
  render () {
    return (
      // 注意這里得用this.props.name, 必須用this.props
      <h1>歡迎進(jìn)入{this.props.name}的世界</h1>
    )
  }
}
ReactDOM.render(
  <App name="react" />,
  document.getElementById('root')
)

運(yùn)行結(jié)果和之前完全一樣,因?yàn)镴S里沒(méi)有真正的class,這個(gè)class只是一個(gè)語(yǔ)法糖, 但二者的運(yùn)行機(jī)制底層運(yùn)行機(jī)制不一樣。

  • 函數(shù)式組件是直接調(diào)用, 在前面的代碼里已經(jīng)有看到

  • es6 class組件其實(shí)就是一個(gè)構(gòu)造器,每次使用組件都相當(dāng)于在實(shí)例化組件,像這樣:

  import React from 'react'
import ReactDOM from 'react-dom'
?
class App extends React.Component {
 render () {
 return (
   <h1>歡迎進(jìn)入{this.props.name}的世界</h1>
  )
  }
}
?
const app = new App({
 name: 'react'
}).render()
?
ReactDOM.render(
 app,
 document.getElementById('root')
)

更老的一種方法

在16以前的版本還支持這樣創(chuàng)建組件, 但現(xiàn)在的項(xiàng)目基本上不用

React.createClass({
  render () {
    return (
      <div>{this.props.xxx}</div>
    )
  }
})

組件的組合、嵌套

將一個(gè)組件渲染到某一個(gè)節(jié)點(diǎn)里的時(shí)候,會(huì)將這個(gè)節(jié)點(diǎn)里原有內(nèi)容覆蓋

組件嵌套的方式就是將子組件寫入到父組件的模板中去,且react沒(méi)有Vue中的內(nèi)容分發(fā)機(jī)制(slot),所以我們?cè)谝粋€(gè)組件的模板中只能看到父子關(guān)系

// 從 react 的包當(dāng)中引入了 React 和 React.js 的組件父類 Component
// 還引入了一個(gè)React.js里的一種特殊的組件 Fragment
import React, { Component, Fragment } from 'react'
import ReactDOM from 'react-dom'
?
class Title extends Component {
  render () {
    return (
      <h1>歡迎進(jìn)入React的世界</h1>
    )
  }
}
class Content extends Component {
  render () {
    return (
      <p>React.js是一個(gè)構(gòu)建UI的庫(kù)</p>
    )
  }
}
/** 由于每個(gè)React組件只能有一個(gè)根節(jié)點(diǎn),所以要渲染多個(gè)組件的時(shí)候,需要在最外層包一個(gè)容器,如果使用div, 會(huì)生成多余的一層dom
class App extends Component {
  render () {
    return (
        <div>
            <Title />
        <Content />
      </div>
    )
  }
}
**/
// 如果不想生成多余的一層dom可以使用React提供的Fragment組件在最外層進(jìn)行包裹
class App extends Component {
  render () {
    return (
      <Fragment>
        <Title />
        <Content />
      </Fragment>
    )
  }
}
ReactDOM.render(
  <App/>,
  document.getElementById('root')
)

JSX 原理

要明白JSX的原理,需要先明白如何用 JavaScript 對(duì)象來(lái)表現(xiàn)一個(gè) DOM 元素的結(jié)構(gòu)?

看下面的DOM結(jié)構(gòu)

<div class='app' id='appRoot'>
  <h1 class='title'>歡迎進(jìn)入React的世界</h1>
  <p>
    React.js 是一個(gè)幫助你構(gòu)建頁(yè)面 UI 的庫(kù)
  </p>
</div>

上面這個(gè) HTML 所有的信息我們都可以用 JavaScript 對(duì)象來(lái)表示:

{
  tag: 'div',
  attrs: { className: 'app', id: 'appRoot'},
  children: [
    {
      tag: 'h1',
      attrs: { className: 'title' },
      children: ['歡迎進(jìn)入React的世界']
    },
    {
      tag: 'p',
      attrs: null,
      children: ['React.js 是一個(gè)構(gòu)建頁(yè)面 UI 的庫(kù)']
    }
  ]
}

但是用 JavaScript 寫起來(lái)太長(zhǎng)了,結(jié)構(gòu)看起來(lái)又不清晰,用 HTML 的方式寫起來(lái)就方便很多了。

于是 React.js 就把 JavaScript 的語(yǔ)法擴(kuò)展了一下,讓 JavaScript 語(yǔ)言能夠支持這種直接在 JavaScript 代碼里面編寫類似 HTML 標(biāo)簽結(jié)構(gòu)的語(yǔ)法,這樣寫起來(lái)就方便很多了。編譯的過(guò)程會(huì)把類似 HTML 的 JSX 結(jié)構(gòu)轉(zhuǎn)換成 JavaScript 的對(duì)象結(jié)構(gòu)。

下面代碼:

import React from 'react'
import ReactDOM from 'react-dom'
?
class App extends React.Component {
  render () {
    return (
      <div className='app' id='appRoot'>
        <h1 className='title'>歡迎進(jìn)入React的世界</h1>
        <p>
          React.js 是一個(gè)構(gòu)建頁(yè)面 UI 的庫(kù)
        </p>
      </div>
    )
  }
}
?
ReactDOM.render(
    <App />,
  document.getElementById('root')
)

編譯之后將得到這樣的代碼:

import React from 'react'
import ReactDOM from 'react-dom'
?
class App extends React.Component {
  render () {
    return (
      React.createElement(
        "div",
        {
          className: 'app',
          id: 'appRoot'
        },
        React.createElement(
          "h1",
          { className: 'title' },
          "歡迎進(jìn)入React的世界"
        ),
        React.createElement(
          "p",
          null,
          "React.js 是一個(gè)構(gòu)建頁(yè)面 UI 的庫(kù)"
        )
      )
    )
  }
}
?
ReactDOM.render(
    React.createElement(App),
  document.getElementById('root')
)

React.createElement 會(huì)構(gòu)建一個(gè) JavaScript 對(duì)象來(lái)描述你 HTML 結(jié)構(gòu)的信息,包括標(biāo)簽名、屬性、還有子元素等, 語(yǔ)法為

React.createElement(
  type,
  [props],
  [...children]
)

所謂的 JSX 其實(shí)就是 JavaScript 對(duì)象,所以使用 React 和 JSX 的時(shí)候一定要經(jīng)過(guò)編譯的過(guò)程:

JSX —使用react構(gòu)造組件,bable進(jìn)行編譯—> JavaScript對(duì)象 — ReactDOM.render()—>DOM元素 —>插入頁(yè)面

組件中DOM樣式

  • 行內(nèi)樣式

想給虛擬dom添加行內(nèi)樣式,需要使用表達(dá)式傳入樣式對(duì)象的方式來(lái)實(shí)現(xiàn):

// 注意這里的兩個(gè)括號(hào),第一個(gè)表示我們?cè)谝狫SX里插入JS了,第二個(gè)是對(duì)象的括號(hào)
 <p style={{color:'red', fontSize:'14px'}}>Hello world</p>

行內(nèi)樣式需要寫入一個(gè)樣式對(duì)象,而這個(gè)樣式對(duì)象的位置可以放在很多地方,例如render函數(shù)里、組件原型上、外鏈js文件中

  • 使用class

React推薦我們使用行內(nèi)樣式,因?yàn)镽eact覺(jué)得每一個(gè)組件都是一個(gè)獨(dú)立的整體

其實(shí)我們大多數(shù)情況下還是大量的在為元素添加類名,但是需要注意的是,class需要寫成className(因?yàn)楫吘故窃趯戭恓s代碼,會(huì)收到j(luò)s規(guī)則的現(xiàn)在,而class是關(guān)鍵字)

<p className="hello" style =  {this.style}>Hello world</p>
  • 不同的條件添加不同的樣式

有時(shí)候需要根據(jù)不同的條件添加不同的樣式,比如:完成狀態(tài),完成是綠色,未完成是紅色。那么這種情況下,我們推薦使用classnames這個(gè)包:

  • css-in-js

styled-components是針對(duì)React寫的一套css-in-js框架,簡(jiǎn)單來(lái)講就是在js中寫css。npm鏈接

TodoList

組件化開(kāi)發(fā)React todolist, 項(xiàng)目開(kāi)發(fā)中的組件的基本目錄結(jié)構(gòu)基本上是這樣的:

注意:一個(gè)組件只干一件事情 ,所以TodoList和TodoItem要做成兩個(gè)組件,這樣也方便于后期理解shouldComponentUpdate

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容