React的基本原理

搭建react運行環境

1.安裝css

npx create-react-app .
複製代碼

注意: 若是網絡走的是代理,那麼本地須要設置代理地址html

npm config set proxy http:// + 你的代理地址
複製代碼
  1. 運行
yarn start
複製代碼

從一個小的demo開始

  • 進入src目錄刪除一些沒必要要的靜態文件(圖片、css)保留js文件,刪除一些靜態文件的importvue

  • 進入index.js,刪除以前全部的代碼從新寫入:react

const div = document.createElement('div')
const p = document.createElement('p')
const span = document.createElement('span')
div.appendChild(p)
p.appendChild(span)
span.innerText = 'Hello World'

document.body.appendChild(div)
複製代碼
  • 第一次優化,建立函數來建立元素
const div = createElement('div')
const p = createElement('p')
const span = createElement('span')
div.appendChild(p)
p.appendChild(span)
span.innerText = 'Hello World'

document.body.appendChild(div)

function createElement(tagName){
    return document.createElement(tagName)
}
複製代碼
  • 第二次優化,使得建立元素的同時能夠往改元素裏面添加一個元素
const div = createElement('div',
                createElement('p',
                    createElement('span')))

document.body.appendChild(div)

function createElement(tagName,children){
    const element = document.createElement(tagName)
    if(children){
        element.appendChild(children)
    }
    return element
}
複製代碼
  • 第三次優化,若是想要穿件span元素的同時往它裏面插入一個文本是不行的,由於函數只接受元素做爲第二個參數,不接受文本做爲參數
const div = createElement('div',
                createElement('p',
                    createElement('span','Hello World!')))

document.body.appendChild(div)

function createElement(tagName,children){
    const element = document.createElement(tagName)
    if(children){
        if(typeof children === 'string'){
            var str = document.createTextNode(children)
            element.appendChild(str)
        } else {
            element.appendChild(children)
        }
    }
    return element
}

複製代碼
  • 第四次優化,把函數名改的更簡單一點
const div = t('div',
                t('p',
                    t('span','Hello World!')))

document.body.appendChild(div)

function t(tagName,children){
    const element = document.createElement(tagName)
    if(children){
        if(typeof children === 'string'){
            var str = document.createTextNode(children)
            element.appendChild(str)
        } else {
            element.appendChild(children)
        }
    }
    return element
}
複製代碼

這樣就能夠經過幾行簡單的代碼,來爲頁面建立嵌套結構的DOM元素了es6

深度截圖_選擇區域_20191014104335.png

  • 第五次優化,改一下格式,經過觀察div內部結構發現雖然是經過函數建立標籤,能不能發明一種語言能夠用像寫標籤同樣寫函數?只要發現「 t(' 」就把它變成'<',只要發現「 ' 」,‘就把它變成'>',第二個參數永遠放到元素的裏面。
const div = (
    t('div',
      t('p',
        t('span', 'Hello World!')))
)

const div2 = (
    <div>
        <p>
            <span>
                Hello World !
            </span>
        </p>
    </div>
)

document.body.appendChild(div)
複製代碼

換句話說能不能開發者寫這樣的代碼:npm

const div2 = (
    <div>
        <p>
            <span>
                Hello World !
            </span>
        </p>
    </div>
)
複製代碼

也就是說有沒有一個方法(bable)能夠直接將上面的代碼翻譯成下面的代碼(具體是怎麼作到的暫時無論):編程

const div = (
    t('div',
      t('p',
        t('span', 'Hello World!')))
)
複製代碼

這一點就是React的創舉!React的核心:看似是在寫標籤其實是react幫咱們翻譯成了函數t,t也就是react.createElement數組

實際上用了react咱們就不須要函數t了,只須要引入reactbash

  • 第六次優化,引入react,將t替換爲react.createElement
import React from 'react'

const div = (
    React.createElement('div',
        React.createElement('p',
            React.createElement('span', 'Hello World!')))
)

console.log(div)
複製代碼

打印出的div不是一個element而是一個虛擬的element(對象),它的類型是div,div裏面是p,p裏面是span,因爲它是虛擬的element,因此不能使用document.body.appendChild(div)直接把它放到body上網絡

深度截圖_選擇區域_20191014111553.png

若是直接放到body上就會報錯:它不是一個節點,而是一個假的節點

深度截圖_選擇區域_20191014112045.png

這就是React的第二個創舉虛擬DOM

  • 那不能將div放到body中那麼建立div還有什麼用,怎麼辦呢?--- 須要引入一個新的庫ReacDOM,它支持虛擬節點
import React from 'react'
import ReactDOM from 'react-dom'

const div = (
    React.createElement('div',
        React.createElement('p',
            React.createElement('span', 'Hello World!')))
)

console.log(div)

ReactDOM.render(div,document.body)

複製代碼

若是直接將div放到body上會警告,不容許污染body,因此放到public文件夾下的index.html中的id爲root的div中

import React from 'react'
import ReactDOM from 'react-dom'

const div = (
    React.createElement('div',null,
        React.createElement('p',null,
            React.createElement('span', null,'Hello World!')))
)

console.log(div)

ReactDOM.render(div,document.getElementById('root'))
複製代碼

注意:React.createElement還須要接收第二個參數null(null參數後面講)

![深度截圖_選擇區域_20191014113414.png](https://i.loli.net/2019/10/14/K8IAnwsTVdOlFQh.png)
複製代碼

到此爲止,一樣的功能就使用React實現了一遍!

React的基本思路: React建立了一個React.createElement的方法,有了這個方法就能夠建立虛擬的element,有了這個虛擬的element,就可使用ReactDOM.render方法將它掛到靜態頁面中body中的元素中

  • 以前那樣寫仍是比較麻煩,react支持另一種寫法一樣能夠實現相同的效果(內心要清楚其實是 React.createElement('div',null,React.createElement('p',null,React.createElement('span', null,'Hello World!'))),只不過爲了書寫更加方便看起來更加直觀,react幫咱們作了一個相似語法糖的東西)
const div = (
    <div>
        <p>
            <span>
                Hello World!
            </span>
        </p>
    </div>
)
複製代碼
  • 如何進行更加複雜的操做?當把Header插入到div時,不能直接寫Header、Bottom不然會把它當成字符串,須要加上"{}"
import React from 'react'
import ReactDOM from 'react-dom'

const Header = (
    <header>
        header
    </header>
)

const Bottom = (
    <div>
        botttom
    </div>
)

const div = (
    <div>
        {Header}
        <p>
            <span>
                Hello World!
            </span>
        </p>
        {Bottom}
    </div>
)
console.log(div)

ReactDOM.render(div,document.getElementById('root'))
複製代碼

這樣咱們就能夠任意的像組合變量同樣組合頁面,這就是React提供的組件化思路和Vue不一樣,vue的組件須要寫vue的單文件組件,vue會發明不少語法,可是React不會,react只是提供了一種簡寫,若是你願意寫React.createElement這種形式,也不會阻止你。

React會使用純JS實現組件化,這也是React的第二個核心思想,使用JS的組合來實現組件化

深度截圖_選擇區域_20191014170403.png

  • 進一步優化,目前的組件Header、Bottom等並不支持參數,JS中什麼支持參數?函數支持加參數。例如:若是Header是一個函數,那麼就能夠接收參數
import React from 'react'
import ReactDOM from 'react-dom'

const Header = (
    <header>
        header
    </header>
)

const Header2 = function(props){
    return (
        <header>
            header {props.name}
        </header>
    )
}
const Bottom = (
    <div>
        botttom
    </div>
)

const div = (
    <div>
        {Header}
        {Header2({name: 'Reagen'})}
        <p>
            <span>
                Hello World!
            </span>
        </p>
        {Bottom}
    </div>
)
console.log(div)

ReactDOM.render(div,document.getElementById('root'))
複製代碼

深度截圖_選擇區域_20191014172651.png

組件Header和組件Header2最大的區別在於,Header是寫死的而Header2是能夠接收參數的

  • 可是上面這種函數式組件的寫法太醜了,支持另一種寫法:
import React from 'react'
import ReactDOM from 'react-dom'

const Header = (
    <header>
        header
    </header>
)

const Header2 = function(props){
    return (
        <header>
            header {props.name}
        </header>
    )
}
const Bottom = (
    <div>
        botttom
    </div>
)

const div = (
    <div>
        {Header}
        {Header2({name: 'Reagen'})}
        <Header2 name = "Jack"/>
        <p>
            <span>
                Hello World!
            </span>
        </p>
        {Bottom}
    </div>
)
console.log(div)

ReactDOM.render(div,document.getElementById('root'))
複製代碼

當React發現標籤裏面不是普通的標籤,而是咱們本身寫的函數式組件的時候,React就會去調用這個函數而且把後面的name = "Jack"當作參數,也就是說當咱們這樣寫:<Header2 name = "Jack"/>React會幫咱們轉化成{Header2({name: 'Jack'})},換句話說{Header2({name: 'Jack'})} 等價於 ====> <Header2 name = "Jack"/>

這也是React和Vue最大的不一樣之處,React不會創造大量的API,React只會在寫法上做文章

  • React中組件裏如何使用自身的變量呢?

例如:組件Bottom2裏面有一個變量n初始值爲0,當點擊按鈕的時候如何讓n每次自增長1?

const Bottom2 = function(){
    let n = 0
    return (
        <div>
            {n}
            <button onClick = {function(){
                n = n + 1
            }}>+1</button>
        </div>
    ) 
}

const div = (
    <div>
        {Header}
        {Header2({name: 'Reagen'})}
        <Header2 name = "Jack"/>
        <p>
            <span>
                Hello World!
            </span>
        </p>
        {Bottom}
        <Bottom2 />
    </div>
)
console.log(div)

ReactDOM.render(div,document.getElementById('root'))
複製代碼

可是這樣點擊按鈕n並不會自增長1

React中使用組件內部的一個狀態應該這樣寫:

const Bottom2 = function(){
    const [n,setN] = React.useState(0)              // es6析構賦值
    return (
        <div>
            {n}
            <button onClick = {function(){
                setN(n+1)
            }}>+1</button>
        </div>
    ) 
}

const div = (
    <div>
        {Header}
        {Header2({name: 'Reagen'})}
        <Header2 name = "Jack"/>
        <p>
            <span>
                Hello World!
            </span>
        </p>
        {Bottom}
        <Bottom2 />
    </div>
)
console.log(div)

ReactDOM.render(div,document.getElementById('root'))
複製代碼

const [n,setN] = React.useState(0)它的意思就是n爲設置n的初始值,若是要改n的值就用setN,這裏使用了一個API

深度截圖_選擇區域_20191014180231.png

  • 函數組件和類組件

Bottom2就是一個函數組件,類組件其實是用的是es6的class語法:

class Bottom3 extends React.Component{
    render(){
        return (
            <div>
                bottom3
            </div>
        )
    }
}

const div = (
    <div>
        {Header}
        {Header2({name: 'Reagen'})}
        <Header2 name = "Jack"/>
        <p>
            <span>
                Hello World!
            </span>
        </p>
        {Bottom}
        <Bottom2 />
        <Bottom3 />
    </div>
)
console.log(div)

ReactDOM.render(div,document.getElementById('root'))
複製代碼

函數式組件更加流行,由於它更簡單

總結: React的特色

1.容許直接使用標籤的形式直接建立虛擬的標籤,裏面能夠嵌套任意的子元素
2.容許把另一個組件經過花括號引進來
3.容許把其餘的組件寫成一個函數,而後把這個函數調用一下再經過花括號引進來
4.固然你也能夠經過另一種形式,把這個組件當作標籤,把參數當作屬性。例如: {Header2({name: 'Jack'})} 等價於 ====> <Header2 name = "Jack"/>
5.若是一個組件你須要它用到本身的狀態就用React.useState(),例如: const [n,setN] = React.useState(0)給了一個讀的API和一個寫的API

用React作一個井字棋的練習

  • 時刻要注意在用react的時候,寫的代碼是和js密切相關的,例如給div加class,就不能寫<div class="xxx">Cell</div>,由於在js中給div加class使用div.className = "xxx",因此這裏應該寫成符合JS語法的形式<div className="xxx">Cell</div>

  • React規定組件中的變量是誰的誰纔有權限改,其餘組件只容許使用這個變量(也就是說這個變量在其餘組件中是隻讀的)

  • 任何函數f均可以被替換成f2:const f2 = (...args) => f(...args)

寫法1:<Cell text={item} onClick = {onClickCell(row,col)}/> 和 寫法2:<Cell text={item} onClick = {() => onClickCell(row,col)}/>

問題: 寫法1和寫法2有什麼不一樣?

答:最終的結果都是同樣,都是調用函數onClickCell,可是過程不同,寫法1會當即調用函數onClickCell,寫法2不會當即執行而是會當Cell被點擊的時候調用onClickCell

  • 深拷貝和淺拷貝在react中的應用

例如: 下面中的cells是一個對象在內存中只是一個地址而已,這裏的cells和聲明cells時候對應的都是同一塊內存地址,即便這裏修改了cells中的某一行某一列爲x,可是cells對應的內存地址並無發生變化,內存地址沒有發生變化,React也就不會更新DOM

const [cells,setCells] = React.useState([
        [null, null, null],
        [null, null, null],
        [null, null, null]
    ])
const onClickCell = function(row,col){
    console.log('行' + row)
    console.log('列' + col)
    cells[row][col] = 'x'
    setCells(cells)                                                               
}
複製代碼

那怎麼辦呢? ===> 深拷貝,有一種簡單的深拷貝的方法:

const copy = JSON.parse(JSON.stringify(cells))
copy[row][col] = 'x'
setCells(copy)   
複製代碼

這樣每次點擊的時候都會從新拷貝一份cells(每一次都是新的內存地址)而後改變其中的某行某列的文本爲x

  • 在React中若是不改變一個對象的內存地址,直接set是沒有用的,也就是說須要深拷貝一份,再在深拷貝的對象上去修改這個對象

  • const [n,setN] = React.useState(0),這裏的n是隻讀的,setN的特色就是它永遠不回去直接去改以前的n而是去複製以前的對象而後搞一個新的再弄回去,學了函數式就會了解這樣的好處

  • React會牽扯到全部的JS知識,JS學的很差就學很差React,React和JS息息相關

  • 面向對象也佔用內存,而後用this去引用這塊內存,函數式編程是特別討厭this的

  • 在判斷誰贏了的時候,當‘x’的某一行3個都是‘x’的時候應該提示的是‘x’贏了,可是爲何只有等‘o’再下一次的時候才告知‘x’贏了?

緣由:setCells(copy)是異步的,因爲cells是一個對象,直接去改裏面的內容如cells[0][0]= 'x',內存的地址不會發生變化仍是以前聲明時候的cells是null所以react不會從新渲染UI頁面不會有任何變化不會出現‘x’,所以須要深拷貝一個對象,這個對象copy是一個新的內存地址,setCells(copy)的時候,因爲每次點擊的是=時候copy每次發生變化都是從新拷貝的內存地址,所以每次copy發生變化以後,react都會從新渲染UI而且cells會跟着copy的變化而變化也就是說它是異步的過程

const onClickCell = function(row,col){
        setN(n+1)
        const copy = JSON.parse(JSON.stringify(cells))
        copy[row][col] = n % 2 == 0 ? 'x' : 'o'
        setCells(copy)                                                                
        result()
    }
複製代碼

可是判斷遊戲結果是根據cells來判斷的而setCells的值是根據copy,當copy發生變化的時候,setCells會跟着變化,頁面會接着渲染出‘x’,可是cells自己並非當即發生變化

const result = function(){
        if(cells[0][0] === cells[0][1] && cells[0][1] === cells[0][2] && cells[0][0] !== null ){
            console.log(cells[0][0] + '贏了')
        }
    }
複製代碼

所以須要將copy做爲參數傳給result,result用參數cells來接收,也就是說在函數result把copy當作cells來判斷

const result = function(cells){
        if(cells[0][0] === cells[0][1] && cells[0][1] === cells[0][2] && cells[0][0] !== null ){
            console.log(cells[0][0] + '贏了')
        }
    }
const onClickCell = function(row,col){
    setN(n+1)
    const copy = JSON.parse(JSON.stringify(cells))
    copy[row][col] = n % 2 == 0 ? 'x' : 'o'
    setCells(copy)                                                                 
    result(copy)
}
複製代碼
  • {finished && <div>遊戲結束</div>}這句話的意思是若是爲finished狀態就展現遊戲結束的div
相關文章
相關標籤/搜索