React 全解 基础

273 阅读9分钟

引入方式

  • 两种方式
    1. CDN 引入(顺序不能错)
    • 引入 React:https://.../react.x.min.js
    • 引入 ReactDOM:...react-dom.x.min.js
      • 两种不同的 React 版本区别:cjs 和 umd
      1. cjs 全称 CommonJS,是 Node.js 支持的模块规范

      2. umd 是统一模块定义,兼容各种模块规范,包括浏览器

      3. 理论上优先使用 umd,同时支持 Node.js 和浏览器

      4. 最新的模块规范是使用 import 和 export 关键字

    1. 使用 React 脚手架
    • 安装 create-react-app
        yarn global add create-react-app
        // or
        npm install create-react-app -g
    
    • 使用命令创建项目
        create-react-app 项目名
    
    • 运行命令yarn start

使用 React

  • 创建一个 React 元素
    import React from 'react'
    import ReactDOM from 'react-dom'
    
    const App = React.createElement('div', null, n)
    // app 是一个 React 元素
    
  • createElement 的返回值 element 可以代表一个 div;但是, element 并不是真正的 div(DOM 对象);我们一般称 element 为 虚拟DOM 对象。
  • 创建一个 React 组件
    ...
    const App = () => 
        React.createElement('div', null, [...])
        
    ReactDOM.render(App(),document.querySelector('#app'))
  • 以上箭头函数作为一个可以返回 element 的函数,也可以代表一个 div,这就是 React 的函数组件。这个函数可以执行多次,每次得到最新的虚拟 div;React 会对比每个虚拟 div,找出不同,局部更新视图;这种找不同的算法叫做 DOM Diff

JSX

  • Vue 有 vue-loader
    • .vue 文件里写 <template>、<script>、<style> 标签时,通过 vue-loader 变成一个构造选项
  • React 有 JSX
    • React 也有自己的编译工具 JSX-loader,但是已经被 babel-loader 取代了,webpack 内置了 babel-loader,所以使用 React 进行开发时,不需要再手动配置。
  • 通过 JSX,我们可以更简洁的使用 React
    import React from 'react'
    import ReactDOM from 'react-dom'
    
    const App = () => {
        return (
            <div>App组件</div>
        )
    }
    
    ReactDOM.render(<App />, document.querySelector('#app'))
几个注意点
  • 注意 className
    • <div className='red'>App组件</div> 会被转译为 React.createElement('div',{className:'red'},'n')
    • 所以类名不能直接使用 class,而是要使用 className
  • 插入变量
    • 标签里面的所有 JS 代码都要用 { } 包起来
    • 如果需要使用变量 n,那么就用 { } 把 n 包起来
    • 如果需要使用对象,那么久要用 { } 把对象包起来,比如:{{name:'zhangsan'}}
  • 习惯 return 后面加 ( )
    • 如果 return 后面换行后,返回值会变成 undefined
React 使用判断语句
    import React from 'react'
    import ReactDOM from 'react-dom'
    
    const Component = () => {
        return (
            n % 2 === 0 ? <div>n 为偶数</div> : <div>n 为奇数</div>
        )
    }
    // or
    const Component = () => {
        return (
            <div>
                {n % 2 === 0 ?<div>n 为偶数</div> : <div>n 为奇数</div>}
            </div>
        )
    }
    // or
    const Component = () => {
        const inner = n % 2 === 0 ?<div>n 为偶数</div> : <div>n 为奇数</div>
        return (
            <div>
                {inner}
            </div>
        )
    }
    // or
    const Component = () => {
        let inner
        if(n % 2 !== 0){
            inner = <div>n 为奇数</div>
        } else {
            inner = <div>n 为偶数</div>
        }
        return (
            <div>
                {inner}
            </div>
        )
    }
  • 以上,React 不同于 Vue, 它的代码非常随意,只要符合规范,随便写,就是在写 JS 而已。
React 使用循环语句
    ...
    const App = (props) => {
        return props.number.map((item, index)=>{
            return (
                <div>
                    {<div>下标为{index}的值为{item}</div>}
                </div>
            )
        })
    }
    // or
    const App = (props) => {
        const array = []
        for (let i = 0; i < props.number.length; i++) {
            array.push(<div>下标为{i}的值为{props.number[i]}</div>)
        }
        return <div>{array}</div>
    }
    // React 会将外部数据放在第一个参数里
    ReactDOM.render(<App numbers={[1, 2, 3]}/>, document.querySelector('#app'))

函数组件和类组件

  • 函数组件
    • 通过参数获取外部数据,React 会将外部数据放在第一个参数里
    function Welcome(props) {
        return <div>Hello, {props.name},welcome to React</div>
    }
    // 使用 
    <Welcome name="zhangsan" />
  • 函数组件实现点击 + 1
    • 函数组件的数据初始化使用 React.useState(),其参数为数据的初始值
    • React.useState 返回一个数组,数组的第一项用来读,第二项用来写
    • setN 不会改变初始数据 n,而是会产生一个新的数据
    import React from 'react'
    import ReactDOM from 'react-dom'
    
    function Add(){
        const [n, setN] = React.setState(0)
        return (
            <div>
                {n}
                <button onClick={()=>setN(n + 1)}> + 1</button>
            </div>
        )
    }
    
    ReactDOM.render(<Add />, document.getElementById('app'))
  • 类组件
    • 通过 this.props 获取外部数据
    class Welcome extends React.Component {
        render() {
            return <div>Hello, {this.props.name},welcome to React</div>
        }
    }
    // 使用
    <Welcome name="zhangsan" />
  • 类组件实现点击 + 1
    • 类组件的数据初始化写在 constructor 函数里
    • 读: this.state.n
    • 写: this.setState({n:this.state.n + 1}) 或者 this.setState((state, props)=> {return {n:state.n + 1}})
      • 要生成一个新的对象,传给 setState
      • setState 是异步的
    import React from 'react'
    import ReactDOM from 'react-dom'
    
    class Add extends React.Component {
        constructor(){
            super()
            this.state = {
                n: 0
            }
        }
        render(){
            return (
                <div>
                    {this.state.n}
                    <button onClick={()=>this.setState({n:this.state.n + 1})}> + 1</button>    
                </div>
            )
        }
    }
    
    ReactDOM.render(<Add />, document.getElementById('app'))
  • 多个数据的情况
    • 如果类组件的数据有多个时,m 或者 n 其中一个变化,React 会自动沿用其他数据之前的旧值。(只有一层,如果修改的是对象内部的数据,也需要手动沿用)
        ...
        class App extends React.Component{
            constructor(){
                super()
                this.state = {
                    n: 0,
                    m: 0
                }
            }
            addN(){
                this.setState({n:this.state.n + 1})
            }
            addM(){
                this.setState({m:this.state.m + 1})
            }
            render(){
                return (
                    <div>
                        {this.state.n}
                        <button onClick={()=>this.addN()}>n + 1</button>
                        {this.state.m}
                        <button onClick={()=>this.addM()}>m + 1</button>
                    </div>
                )
            }
        }
        ...
    
    • 如果函数组件数据有多个时,m 或者 n 其中一个变化,React 不会自动沿用其他数据之前的旧值,则需要手动沿用。
        ...
        const App = () => {
            const [state, setState] = React.useState({
                n: 0,
                m: 0
            })
            return (
                <div>
                    {n}
                    <button onClick={()=>setState({...state, n:state.n + 1})}>n + 1</button>
                    {m}
                    <button onClick={()=>setState({...state, m:state.m + 1})}>n + 1</button>
                </div>
            )
        }
        ...
    

React 事件绑定

类组件的事件绑定
    ...
    add(){
        this.setState(state => {
            return {n:state.n + 1}
        })
    }
    render(){
        return (
        // 最安全的写法
            <div>
                <button onClick={() => this.add()}>n + 1</button>
            </div>
        // 以下写法会使 this 变成 window。代码执行时,React 会调用 button.onClick.call(null, event),add 函数里的 this 就会变成 window
            <div>
                <button onClick={this.add}>n + 1</button>
            </div>
        // 使用 bind 来指定 this 是正确的,它返回了一个绑定当前 this 的新函数,但是略复杂
            <div>
                <button onClick={this.add.bind(this)}>n + 1</button>
            </div>
        // 或者将函数起个名字: this._add = () => this.add()
            <div>
                <button onClick={this._add}>n + 1</button>
            </div>
        )
    }    
    ...
  • 又要给函数起一个新的名字还是很麻烦,我们还有更好的方法
    import React from 'react'
    import ReactDOM from 'react-dom'
    
    class App extends React.Component {
        constructor() {
            super()
            this.add = () => this.setState(state=>{
                return {n: state.n + 1}
            })
        }
        /* 或者直接写在外面
         add = () => this.setState(state => {
            return {n: state.n + 1}
        }) */
        render(){
            return (
                <div>
                    <button onClick={this.add}>n + 1</button>
                </div>
            )
        }
    }

Props 外部数据

Props 的作用
  • 接收外部数据
    • 只能读不能写
    • 外部数据由父组件传递
  • 接收外部函数
    • 在恰当的时机,调用该函数
    • 该函数一般是父组件的函数
类组件 props
  • 传入 props 给 B 组件
    • 外部数据被包装成一个对象:{name:'zhangsan',onClick:..., children:'hello'}
    • 此处的 onClick 是一个回调
    ...
    class Parent extends React.Component {
        constructor(props) {
            super(props)
            this.state = {name: 'zhangsan'}
        }
        onClick = () => {}
        // 组件的外部数据一般来自于父组件的内部数据
        render(){
            return <B name={this.state.name} 
                    onClick={this.onClick}>hello</B>
        }
    }
    ...
  • B 组件初始化外部数据
    • 这样做了之后, this.props 就是外部数据对象的地址
    ...
    class B extends React.Component {
        // 如果 constructor 没有其他数据,可以忽略不写
        constructor(props) {
            super(props)
        }
        render(){}
    }
    ...
  • 不要在 B 组件内部修改 props

State 内部数据

类组件

初始化 state
    ...
    class B extends React.Component {
        constructor(props) {
            super(props)
            this.state = {
                user: {name: 'zhangsan', age: 18}
            }
        }
        render(){...}
    }
    ...
读取 state

this.state.xx.yy

修改 state
  • 直接传入一个新的 state: this.setState(newState,fn)
    • setState 不会立刻改变 this.state,会在当前代码执行完毕后,再去更新 this.state,从而触发 UI 更新
  • 使用函数:this.setState((state,props)=>newState,fn)
  • fn 会在写入成功后执行
  • setState 会自动将新 state 与旧 state 进行一级合并

生命周期

constructor() 构造、初始化

  • 在这里初始化 state、props

shouldComponentUpdate(newProps, newState) 是否更新

  • return false 阻止更新
  • 它允许我们手动判断是否要进行组件更新,我们可以根据应用场景灵活的设置返回值,以避免不必要的更新
  • React 内置了此功能,在创建组件的时候,使用 PureComponent,PureComponent 会在 render 之前对比新 state 和旧 state 的每一个 key,以及新 props 和旧 props 的每一个 key。如果所有 key 的值全都一样,就不会 render;如果有任何一个 key 的值不同,就会 render。
    ...
    class B extends React.PureComponent {}
    ...

render() 渲染

  • 创建虚拟 DOM
  • 用于展示视图
    • return (<div>...</div>)
    • 如果有多个根元素,要用 <React.Fragment> 包起,可以缩写成 <></>
        ...
        render(){
            return (
                <>
                    <div>...</div>
                    <div>...</div>
                </>
            )
        }
        ...
    
  • render 里面可以写 if..else、三元表达式
    ...
    render(){
        let message
        if(this.state.n % 2 === 0) {
            message = <div>偶数</div>
        } else {
            message = <div>奇数</div>
        }
        
        return (
            <>
                {message}
                <button onClick={this.onClick}>n + 1</button>
            </>
        )
    }
    ...
  • render 里面不能直接写 for 循环(无返回值),需要用数组
    ...
    render(){
        let result = []
        for(let i = 0; i < this.state.array.length; i++){
            result.push(this.state.array[i])
        }
        return result
    }
    ...
  • render 里面可以写 map 循环
    ...
    render(){
        return this.state.array.map(n => <span key={n}>{n}</span>)
    }
    ...

componentDidMount() 组件挂载完成

  • 组件已经出现在页面
  • 用于在元素插入页面后执行代码,这些代码一般依赖 DOM
// 展示一个div元素的宽度
    ...
    class App extends React.PureComponent {
        constructor(props){
            super(props)
            this.state = {
                width: undefined
            }
        }
        componentDidMount(){
            const div = document.getElementById('xx')
            const {width} = div.getBoundingClientRect() // 返回元素的大小及其基于视口的位置
            this.setState({width:width})
        }
        render(){
            return (
                <div id="xx">{this.state.width}</div>
            )
        }
    }
    ...
  • React 提供了 React.creatRef(),以更方便的选取 DOM 元素
    ...
    class App extends React.PureComponent {
        constructor(props){
            super(props)
            this.state = {
                width: undefined
            }
            // 1. 创建一个 ref
            this.difRef = React.creatRef()
        }
        componentDidMount(){
        // 3. 获取
            const div = this.divRef.current
            const {width} = div.getBoundingClientRect()
            this.setState({width})
        }
        render(){
            return (
            // 2. 挂载 
                <div ref={this.divRef}>{this.state.width}</div>
            )
        }
    }
  • 此处可以发起加载数据的 AJAX 请求(官方推荐)
  • 首次渲染会执行此钩子

componentDidUpdate(preProps,prevState,snapshot) 组件更新完成

  • 组件已经更新
  • 在视图更新后执行代码
  • 此处也可以发起 AJAX 请求,用于更新数据
  • 首次渲染不会执行此钩子
  • 在此处 setState 可能会引起无限循环,除非放在 if 里
  • shouldComponentUpdate(newProps, newState)返回 false,则不会触发此钩子

componentWillUnmount() 组件将要卸载前

  • 组件将要被移除页面然后被销毁时执行代码
  • unmount 过的组件不会再次 mount
    • 如果在componentDidMount里面监听了window. scroll,那么就要在componentWillUnmount里面取消监听
    • 如果在componentDidMount里面创建了Timer定时器,那么就要在componentWillUnmount里面取消定时器
    • 如果在componentDidMount里面创建了 AJAX 请求,那么就要在componentWillUnmount里面取消请求

函数组件的生命周期

函数组件要比 class 组件简洁的多,但是函数组件没有 state 和声明周期
没有 State
  • React v16.8.0 推出了 Hooks API,其中的一个 API 叫做 useState 可以解决
没有声明周期
  • Hooks API 中的 useEffect 可以模拟生命周期
    • useEffect 的第二个参数接受一个数组,当数组中的任意数据变化时,第一个参数回调里的代码就会执行,如果数组为空,则只会在第一次渲染时执行一次,如果直接不写,那任何一个数据变化都会执行前面的回调,如果 useEffect 返回了一个函数,那么这个函数会在组件即将销毁时执行。
    • 自定义 Hook,解决第一次渲染执行的问题,只在数据变化时执行
        import React,{useState,useEffect} from 'react'
        import ReactDOM from 'react-dom'
        
        const useUpdate = (fn, dep) => {
            const [count, setCount] = useState(0)
            useEffect(()=>{
                setCount(x => x+1)
            }, [dep])
            
            useEffect(()=> {
                if(count > 1){
                    fn()
                }
            }, [count, fn])
        }
        const App = ()=> {
            const [n, setN] = useState(0)
            const onClick = () => {
                setN(n + 1)
            }
            useUpdate(()=>{
                console.log('n 变了')
            }, n)
            return (
                <div>
                    {n}
                    <button onClick={onClick}> + 1</button>
                </div>
            )
        }
        ReactDOM.render(<App />,document.getElementById('app'))
    
  • 模拟 componentDidMount
    useEffect(()=>{ console.log('第一次渲染') }, [])
  • 模拟 componentDidUpdate
    useEffect(()=>{ console.log('任意属性变更') })
    useEffect(()=>{ console.log('n 变了') }, [n])
  • 模拟 componentWillUnmount
    useEffect(()=>{
        console.log('第一次渲染')
        return ()=>{
            console.log('组件即将消亡')
        }
    })
  • constructor
    • 函数组件执行的时候,就相当于 constructor
  • shouldComponentUpdate
    • React.memouseMemo 可以解决
  • render
    • 函数组件的返回值就是 render 的返回值

React 和 Vue 的区别

相同点
  • 都是对视图的封装,React 是用类和函数表示一个组件,而 Vue 是通过构造选项构造一个组件
  • 都提供了 createElement 的 XML 简写,React 提供的是 JSX 语法,而 Vue 提供的是模板写法(语法巨多)
不同点
  • React 是把 HTML 放在 JS 里面写(HTML in JS),而 Vue 是把 JS 放在 HTML 里面写(JS in HTML)