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
相关文章
相关标签/搜索