React学习之路·

307 阅读7分钟

如何引入React

从CDN引入React

//引入React:
https://.../react.x.min.js
//引入ReaatDOM:https://.../react-dom.x.min.js

cjs和umd的区别
cjs全称是CommonJS,是Node.js支持的模块规范
umd是统一模块定义,兼容各种模块规范
理论上优先使用umd,同时支持Node.js和浏览器
最新的模块规范是使用import和export关键字

通过WebPack引入React

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

//注意大小写,尽量保持一致

对比React元素和函数组件

对比

App1 = React.createElement('div',null,n)
App是一个React元素
App = ()=> React.createElement('div',null,n)
App2是一个React函数组件

//函数App2是延迟执行的代码,会在被调用的时候执行

React元素

  • createElement的返回值element可以代表一个div
  • 但element并不是真正的div(DOM对象)
  • 所以我们一般称element为虚拟DOM对象

()=>React元素

  • 返回element的函数,也可以代编一个div
  • 这个函数可以多次执行,每次得到最新的虚拟div
  • React会对比两个虚拟div,找出不同(找出不同的算法就叫做DOM Diff算法),局部更新试图

JSX

使用JSX

方法一:CDN

  • 引入babel.min.js
  • <script>改成<script type="text/babel">
  • babel会自动进行转译

方法二:webpack+babel-loader

方法三:使用create-react-app

yarn global add create-react-app
create-react-app react-demo-1
cd react-demo-1

注意事项

  • 注意className
<div className="red">n</div>
被转译为
React.createElement('div',{className:'red'},"n")
  • 插入变量 标签里面的所有JS代码都要用{}括起来; 如果需要变量n,就用{}把n包起来 如果需要对象,就要用{}把对象包起来,例如{{name:'wan-ti'}}
  • 习惯在return后面加()

React的两种组件

函数组件

function Welcome(props){
    return <h1>Hello,{props.name}</h1>
}

使用方法:<Welcome name="wan-ti" />

函数组件注意事项

类组件

class Welcome extends React.Component {
    render() {
        return <h1>Hello, {this.props.name}</h1>
    }
}
使用方法:<Welcome name="wan-ti">

React.createElement的逻辑

  • 如果传入一个字符串 'div',则会创建一个div
  • 如果传入一个函数,则会调用该函数,获取其返回值
  • 如果传入一个类,则在类前面加一个new,获取一个组件对象,然后调用对象的render方法,获取其返回值。

类组件注意事项

以代码为例


两种编程模型

Vue的变成模型

一个对象,对应一个虚拟DOM。
当对象的属性改变时,把属性相关的DOM节点全部更新
Vue为了其他的考量,同样引入了虚拟DOM和DOM diff

React的编程模型

一个对象,对应一个虚拟DOM。
另一个对象,对应另一个虚拟DOM
对比两个虚拟DOM,找不同(DOM diff),最后局部更新DOM

事件绑定

类组件的事件绑定

//写法一
class Son extends React.Component{
    addN = () => this.setState({n:this.state.n+1});
    render(){
        return <button onClick={this.addN}>n+1</button>
    }
    }
 //写法二:
class Son extends React.Component{
    addN = () => this.setState({n:this.state.n+1});
    addN(){
       this.setState({n:this.state.n+1})
    }
    }
写法一和写法二的addN的区别在:
写法一的addN在对象上,写法二的addN在原型上;
写法二的this可变成window,写法一不会;

写法一是对象本身的属性,意味着每个Son组件都有自己的addN,如果有两个Son,就有两个addN;
写法二是对象的共有属性(也就是原型上的属性),也就是说所有的Son组件共用一个addN

Class组件详解

两种方式创建Class组件

//ES5的方式
import React from 'react'
const A =React.createClass({
    render(){
        return (
        <div>hi</div>
        )
    }
})

export default A

//ES6方式
import React from 'react'
class V extends React.Component{
    super(props);
}
render(){
    return (
    <div>hi</div>
    )
    }
}
export default B;

Props-外部数据

//初始化
class B extends React.Component {
    constructor(props){
        super(props);
    }
//读取
render(){
    return <div onClick={this.props.onClick}>
    {this.props.name}
        <div>
            {this.props.children}
        </div>
    </div
}
}//通过this.props.xxx读取

Props的作用

接受外部数据

  • 只能读不能写
  • 外部数据由父组件传递

接受外部函数

  • 恰当的时机,调用该函数
  • 该函数一般是父组件的函数

State&setState

//初始化
class B extends React.Component {
    constauctor(props) {
        super(props);
        this.state = {
            user:{name:'wan-ti',age:18}
        }
    }
//读写
    render(){
        //读
        this.state.xxx.yyy.xxx;
        //写
        this.setState(newState,fn);
        //setState不会立刻改变this.state,会在当前代码运行完后,再去更新this.state,从而触发UI更新
        this.setState((state,props) => newState,fn)
        //如果这么写回更容易理解一点
    }
}

生命周期

函数列表

constructor()-在这里初始化state

static getDerivedStateFromProps()

shouldComponentUpdate-return false阻止更新

render()-创建虚拟DOM

getSnaphshotBeforeUpdate()

componentDidMount()-组件已更新在页面

componentDidUpdate-组件已更新

componentWillUnmount()--组件要隔屁了

static getDerivedStateFromError()

componentDidCatch()

constructor

用途:

  • 初始化props
  • 初始化state,但此时不能调用setState
  • 用来写bind this
constructor(){
    //写法一:
    ...
    this.onClick = this.onClick.bind(this)
    //写法二:
    onClick = () => {}
    constructor(){
        ...
    }
}
  • 也可以不写
shouldComponentUpdate

用途

  • 返回true表示不阻止UI更新
  • 返回false表示阻止UI更新 示例代码:
render

用途

  • 展示试图 return (<div>...</div>)
  • 只能有一个根元素
  • 如果有两个根元素,需要用<React.Fragment>包起来
  • <React.Fragment />可以缩写为<></>

小技巧

  • render里面可以写if...else
  • render里面可以写?:表达式
  • render里不可以直接写for循环,需要借用数组
  • render里可以写array.map用于循环
componentDidMount

用途:

  • 在元素插入页面后执行代码,这些代码依赖DOM
  • 可发起加载数据的AJAX请求
  • 首次渲染回执行此钩子
componentDidUpdate()

用途:

  • 视图更新后执行代码
  • 发起AJAX请求,用于更新数据
  • 首次渲染并不会执行这个钩子
  • 在这里使用setState会引起无限循环,可放在if里
  • 若shouldCom...Update返回false,不触发这个钩子
componenWillUnmount

用途:

  • 组件将要被移出页面然后销毁的时候执行代码
  • unmount过的组件不会再次mount
钩子的执行顺序

函数组件详解

创建方式

const Hello = (props) => {
    return <div>{props.message}</div>
}
const Hello = props => <div>{props.message}</div>
function Hello(props){
    return <div>{props.message}</div>
}

特点:

  • 没有state
  • 没收生命周期

useEffect

//模拟componentDidMount
useEffect(()=>{ console.log('第一次渲染') },[])
//模拟componentDidUpdate
useEffect(()=>{ console.log('任意属性变更')})
useEffect(()=>{ console.log('n变了 ')}, [n])
//模拟componentWillUnmount
useEffect(()=>{
console.log('第一次渲染 ')
return ()=>{
console.log('组件要嗝屁了')
}
})

useState

尝试实现React.useState

let _state;
function muUseState(initialValue){
    _state = _state === undefined ? initialValue : _state;
    function setState(newState) {
        _state = newState;
        render();
    }
    return [_state,setState]
}

const render = () => ReactDOM.render(<App />,rootElement);

多个useState

let _state = [];
let index = 0;

function myUseState(initialValue) {
    const currentIndex = index;
    index += 1;
    _state[currentIndex] = _state[currentIndex] || initialValue;
    const setState = newState = > {
        _state[currentIndex] = newState;
        render();
    };
    return [_state[currentIndex,setState]
}

const render = () => {
    index = 0;
    ReactDOM.render(<App />,rootElement);
}

App组件更新过程

总结

  • 每个函数组件对应一个React节点*
  • 每个节点保存着state和index
  • useState会读取state[index]
  • index由useState出现的顺序决定
  • setState会修改state,并触发更新
  • 每次重新渲染,组件函数就会执行
  • 对应的所有state都会出现 "分身"
  • 如果不希望分身,可以选择使用useRef/useContext

React节点应该是FiberNode,_state的真实名字为memorizedState,index的实现用到了链表

Reack Hooks

useState

使用状态

const [n,setN]=React.useState(0)
const [user,setUser] =React.useState({name:'W'})

注意:

  • 不可局部更新
  • 数据变化的前提是地址发生变化

useState接受函数

const [state,setState] = useState(() => {
    return initialState
})
//该函数返回初始state,且只执行一次

setState接受函数

setN(i => i+1)

useReducer

  • 创建初始值initialState
  • 创建所有操作reducer(state,action)
  • 传给u色Reducer,得到读和写API
  • 调用写({type:'操作类型'})

如何代替Redux 1:将数据集中在一个store对象
2:将所有操作集中在reducer
3:创建一个Context
4:创建对数据的读写API
5:将第四步的内容放到第三步的Context
6:用Context.Provider将Context提供给所有组件
7:各个组件用useContext获取读写API

useEffect

副作用

  • 会改变环境,例如修改document.title

用途:

  • 作为componentDidMount使用,[]作第二个参数
  • 作为componentDidUpdate使用。可指定依赖
  • 作为componentWillUnmount使用,通过return
  • 以上三种可同时存在

特点:

如果同时存在对各useEffect,会按照出现次序执行

useLayoutEffect

布局副作用:

  • useEffect在浏览器渲染完成后执行
  • useLayoutEffect在浏览器渲染前执行

特点:

  • useLayoutEffect总是比useEffect先执行
  • useLayoutEffect里的任务非常容易影响Layout

经验:

为了用户体验,优先使用useEffect

useMemo

特点:

  • 第一个参数是()=>value
  • 第二个参数是依赖[m,n]
  • 只有当依赖变化时,才会计算出新的value
  • 如果依赖不变,就会重用之前的value

注意:

如果value是个函数,需要写成useMemo(() => (x) => console.log(x)) 这是一个返回函数的函数

useCallback

用法:

useCallback(x => log(x),[m])
等价于
useMemo(() => x => log(x),[m])

useRef

目的:

在组件不断render时保持不变
初始化:const count = useRef(0)
读取:count.current

forwardRef

useRef

  • 可以用来引用DOM对象
  • 可以用来引用普通对象

useImperativeHandle

自定义Hook

Stale Closure