React 学习笔记

参考文章:React入门看这篇就够了

为什么要用React

  • 1 使用组件化开发方式,符合现代 Web 开发的趋势
  • 2 技术成熟,社区完善,配件齐全,适用于大型Web项目(生态系统健全)
  • 3 由Facebook专门的团队维护,技术支持可靠
  • 4 ReactNative - Learn once, write anywhere: Build mobile apps with React
  • 5 使用方式简单,性能非常高,支持服务端渲染
  • 6 React非常火,从技术角度,可以满足好奇心,提高技术水平;从职业角度,有利于求职和晋升,有利于参与潜力大的项目

React中的核心概念

  • 1 虚拟DOM(Virtual DOM)
  • 2 Diff算法(虚拟DOM的加速器,提升React性能的法宝)

image-20200728142925684

虚拟DOM(Vitural DOM)

React 将 DOM 抽象为虚拟 DOM,虚拟 DOM 其实就是用一个对象来描述 DOM,通过对比前后两个对象的差异,最终只把变化的部分重新渲染,提高渲染的效率

为什么用虚拟 Dom,当 Dom 反生更改时需要遍历 而原生 Dom 可遍历属性多达231个 且大部分与渲染无关 更新页面代价太大

VituralDOM的处理方式

  • 1 用 JavaScript 对象结构表示 DOM 树的结构,然后用这个树构建一个真正的 DOM 树,插到文档当中
  • 2 当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异
  • 3 把2所记录的差异应用到步骤1所构建的真正的 DOM 树上,视图就更新了

Diff算法

Reconciliation diff

diff算法 - 中文文档

不可思议的 react diff

React diff 算法

React创建组件的两种方式

  • 1 通过 JS函数 创建(无状态组件)
  • 2 通过 class 创建(有状态组件)

函数式组件 和 class 组件的使用场景说明:
1 如果一个组件仅仅是为了展示数据,那么此时就可以使用 函数组件
2 如果一个组件中有一定业务逻辑,需要操作数据,那么就需要使用 class 创建组件,因为,此时需要使用 state

JavaScript函数创建

  • 注意:1 函数名称必须为大写字母开头,React通过这个特点来判断是不是一个组件
  • 注意:2 函数必须有返回值,返回值可以是:JSX对象或null
  • 注意:3 返回的JSX,必须有一个根元素
  • 注意:4 组件的返回值使用()包裹,避免换行问题
function Welcome(props) {
    return (
        // 此处注释的写法 
        <div className="shopping-list">
            {/* 此处 注释的写法 必须要{}包裹 */}
            <h1>Shopping List for {props.name}</h1>
            <ul>
                <li>Instagram</li>
                <li>WhatsApp</li>
            </ul>
        </div>
    )
}

ReactDOM.render(
    <Welcome name="jack" />,
    document.getElementById('app')
)

class创建

在es6中class仅仅是一个语法糖,不是真正的类,本质上还是构造函数+原型 实现继承

// ES6中class关键字的简单使用

// - **ES6中的所有的代码都是运行在严格模式中的**
// - 1 它是用来定义类的,是ES6中实现面向对象编程的新方式
// - 2 使用`static`关键字定义静态属性
// - 3 使用`constructor`构造函数,创建实例属性
// - [参考](http://es6.ruanyifeng.com/#docs/class)

// 语法:
class Person {
    // 实例的构造函数 constructor
    constructor(age){
        // 实例属性
        this.age = age
    }
    // 在class中定义方法 此处为实例方法 通过实例打点调用
    sayHello () {
        console.log('大家好,我今年' + this.age + '了');
    }

    // 静态方法 通过构造函数打点调用 Person.doudou()
    static doudou () {
        console.log('我是小明,我新get了一个技能,会暖床');
    }
}
// 添加静态属性
Person.staticName = '静态属性'
// 实例化对象
const p = new Person(19)


// 实现继承的方式

class American extends Person {
    constructor() {
        // 必须调用super(), super表示父类的构造函数
        super()
        this.skin = 'white'
        this.eyeColor = 'white'
    }
}

// 创建react对象
// 注意:基于 `ES6` 中的class,需要配合 `babel` 将代码转化为浏览器识别的ES5语法
// 安装:`npm i -D babel-preset-env`

//  react对象继承字React.Component
class ShoppingList extends React.Component {
    constructor(props) { 
        super(props)
    }
    // class创建的组件中 必须有rander方法 且显示return一个react对象或者null
    render() {
        return (
            <div className="shopping-list">
                <h1>Shopping List for {this.props.name}</h1>
                <ul>
                    <li>Instagram</li>
                    <li>WhatsApp</li>
                </ul>
            </div>
        )
    }
}

state vs props

state 是让组件控制自己的状态,props 是让外部对组件自己进行配置。

尽量少地用 state,尽量多地用 props

列表渲染

const users = [
    { username: 'Jerry', age: 21, gender: 'male' },
    { username: 'Tony', age: 22, gender: 'male' },
    { username: 'Lily', age: 19, gender: 'female' },
    { username: 'Lucy', age: 20, gender: 'female' }
]

class User extends Component {
    render () {
        const { user } = this.props
        return (
            <div>
                <div>姓名:{user.username}</div>
                <div>年龄:{user.age}</div>
                <div>性别:{user.gender}</div>
                <hr />
            </div>
        )
    }
}

class Index extends Component {
    render () {
        return (
            // 没有 key 的话控制台会报错,但这种方法有些掩耳盗铃
            <div>
                {users.map((user, index) => <User key={index} user={user} />)}
            </div>
        )
    }
}

prevState

组件生命周期

组件生命周期 - 创建阶段(Mounting)

  • 特点:该阶段的函数只执行一次

constructor()

  • 作用:1 获取 props; 2 初始化state
  • 说明:通过 constructor() 的参数props获取
  • 设置state和props
class Greeting extends React.Component {
    constructor(props) {
        // 获取 props
        super(props)
        // 初始化 state
        this.state = {
            count: props.initCount
        }
    }
}

// 初始化 props
// 语法:通过静态属性 defaultProps 来初始化props
Greeting.defaultProps = {
    initCount: 0
};

componentWillMount()

  • 说明:组件被挂载到页面之前调用,其在render()之前被调用,因此在这方法里同步地设置状态将不会触发重渲染
  • 注意:无法获取页面中的DOM对象
  • 注意:可以调用 setState() 方法来改变状态值
  • 用途:发送ajax请求获取数据
componentWillMount() {
    console.warn(document.getElementById('btn')) // null
    this.setState({
        count: this.state.count + 1
    })
}

render()

  • 作用:渲染组件到页面中,无法获取页面中的DOM对象
  • 注意:不要在render方法中调用 setState() 方法,否则会递归渲染
    • 原因说明:状态改变会重新调用render()render()又重新改变状态
render() {
    console.warn(document.getElementById('btn')) // null

    return (
        <div>
            <button id="btn" onClick={this.handleAdd}>打豆豆一次</button>
            {
                this.state.count === 4
                    ? null
                : <CounterChild initCount={this.state.count}></CounterChild>
            }
        </div>
    )
}

componentDidMount()

  • 1 组件已经挂载到页面中
  • 2 可以进行DOM操作,比如:获取到组件内部的DOM对象
  • 3 可以发送请求获取数据
  • 4 可以通过 setState() 修改状态的值
  • 注意:在这里修改状态会重新渲染
componentDidMount() {
    // 此时,就可以获取到组件内部的DOM对象
    console.warn('componentDidMount', document.getElementById('btn'))
}

组件生命周期 - 运行阶段(Updating)

  • 特点:该阶段的函数执行多次
  • 说明:每当组件的props或者state改变的时候,都会触发运行阶段的函数

componentWillReceiveProps()

  • 说明:组件接受到新的props前触发这个方法
  • 参数:当前组件props
  • 可以通过 this.props 获取到上一次的值
  • 使用:若你需要响应属性的改变,可以通过对比this.propsnextProps并在该方法中使用this.setState()处理状态改变
  • 注意:修改state不会触发该方法
componentWillReceiveProps(nextProps) {
  console.warn('componentWillReceiveProps', nextProps)
}

shouldComponentUpdate()

  • 作用:根据这个方法的返回值决定是否重新渲染组件,返回true重新渲染,否则不渲染
  • 优势:通过某个条件渲染组件,降低组件渲染频率,提升组件性能
  • 说明:如果返回值为false,那么,后续render()方法不会被调用
  • 注意:这个方法必须返回布尔值!!!
  • 场景:根据随机数决定是否渲染组件
// - 参数:
//   - 第一个参数:最新属性对象
//   - 第二个参数:最新状态对象
shouldComponentUpdate(nextProps, nextState) {
    console.warn('shouldComponentUpdate', nextProps, nextState)

    return nextState.count % 2 === 0
}

componentWillUpdate()

  • 作用:组件将要更新
  • 参数:最新的属性和状态对象
  • 注意:不能修改状态 否则会循环渲染
componentWillUpdate(nextProps, nextState) {
    console.warn('componentWillUpdate', nextProps, nextState)
}

render() 渲染

  • 作用:重新渲染组件,与Mounting阶段的render是同一个函数
  • 注意:这个函数能够执行多次,只要组件的属性或状态改变了,这个方法就会重新执行

componentDidUpdate()

  • 作用:组件已经被更新
  • 参数:旧的属性和状态对象
componentDidUpdate(prevProps, prevState) {
    console.warn('componentDidUpdate', prevProps, prevState)
}

组件生命周期 - 卸载阶段(Unmounting)

  • 组件销毁阶段:组件卸载期间,函数比较单一,只有一个函数,这个函数也有一个显著的特点:组件一辈子只能执行依次!
  • 使用说明:只要组件不再被渲染到页面中,那么这个方法就会被调用( 渲染到页面中 -> 不再渲染到页面中 )

componentWillUnmount()

  • 作用:在卸载组件的时候,执行清理工作,比如
    • 1 清除定时器
    • 2 清除componentDidMount创建的DOM对象

dangerouslySetInnerHTML

高阶组件

src/wrapWithLoadData.js

import React, { Component } from 'react'

export default (WrappedComponent, name) => {
    class NewComponent extends Component {
        constructor () {
            super()
            this.state = { data: null }
        }

        componentWillMount () {
            let data = localStorage.getItem(name)
            this.setState({ data })
        }

        render () {
            return <WrappedComponent data={this.state.data} />
        }
    }
    return NewComponent
}

src/InputWithUserName.js

import wrapWithLoadData from './wrapWithLoadData'

class InputWithUserName extends Component {
    render () {
        return <input value={this.props.data} />
    }
}

InputWithUserName = wrapWithLoadData(InputWithUserName, 'username')
export default InputWithUserName

context (全局参数)

import React, { Component } from 'react'

class Index extends Component {
    static childContextTypes = {
        themeColor: PropTypes.string
    }

    constructor () {
        super()
        this.state = { themeColor: 'red' }
    }

    getChildContext () {
        return { themeColor: this.state.themeColor }
    }

    render () {
        return (
            <div>
                <Header />
                <Main />
            </div>
        )
    }
}

class Title extends Component {
    static contextTypes = {
        themeColor: PropTypes.string
    }

    render () {
        return (
            <h1 style={{ color: this.context.themeColor }}>React.js 小书标题</h1>
        )
    }
}

react-router 路由跳转

npm install -S react-router

npm i -S react-router-dom
(安装 react-router-dom 时会自动安装 react-router)

react-router v3 与 v4 的区别


import React from 'react'
import {Router, Route, BrowserRouter, Link} from 'react-router-dom'

import CommentApp from '../Comment/containers/CommentApp'
import Game from "../Game/Game";
import Clock from "../Clock/Clock";

const BaseLayout = () => (
    <div className="base">
        <header>
            <nav>
                <ul>
                    <li><Link to={"/game"}>Game</Link></li>
                    <li><Link to={"/clock"}>Clock</Link></li>
                    <li><Link to={"/comment/Samuel Liu"}>Comment</Link></li>
                </ul>
            </nav>
        </header>
        <div className="container">
            <Route path="/game" exact component={Game}/>
            <Route path="/clock" component={Clock}/>
            {/* 传参 username */}
            <Route path="/comment/:username" component={CommentApp}/>
        </div>
    </div>
);

const SubRouter = () => (
    <BrowserRouter>
        <BaseLayout/>
    </BrowserRouter>
)

export default SubRouter

Redux

npm install redux react-redux –save

单一数据源

整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。

这让同构应用开发变得非常容易。来自服务端的 state 可以在无需编写更多代码的情况下被序列化并注入到客户端中。由于是单一的 state tree ,调试也变得非常容易。在开发中,你可以把应用的 state 保存在本地,从而加快开发速度。此外,受益于单一的 state tree ,以前难以实现的如“撤销/重做”这类功能也变得轻而易举。

console.log(store.getState())

/* 输出
{
  visibilityFilter: 'SHOW_ALL',
  todos: [
    {
      text: 'Consider using Redux',
      completed: true,
    },
    {
      text: 'Keep all state in a single tree',
      completed: false
    }
  ]
}
*/

State 是只读的

唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。

这样确保了视图和网络请求都不能直接修改 state,相反它们只能表达想要修改的意图。因为所有的修改都被集中化处理,且严格按照一个接一个的顺序执行,因此不用担心 race condition 的出现。 Action 就是普通对象而已,因此它们可以被日志打印、序列化、储存、后期调试或测试时回放出来。

store.dispatch({
  type: 'COMPLETE_TODO',
  index: 1
})

store.dispatch({
  type: 'SET_VISIBILITY_FILTER',
  filter: 'SHOW_COMPLETED'
})

使用纯函数来执行修改

为了描述 action 如何改变 state tree ,你需要编写 reducers。

Reducer 只是一些纯函数,它接收先前的 state 和 action,并返回新的 state。刚开始你可以只有一个 reducer,随着应用变大,你可以把它拆成多个小的 reducers,分别独立地操作 state tree 的不同部分,因为 reducer 只是函数,你可以控制它们被调用的顺序,传入附加数据,甚至编写可复用的 reducer 来处理一些通用任务,如分页器。

function visibilityFilter(state = 'SHOW_ALL', action) {
  switch (action.type) {
    case 'SET_VISIBILITY_FILTER':
      return action.filter
    default:
      return state
  }
}

function todos(state = [], action) {
  switch (action.type) {
    case 'ADD_TODO':
      return [
        ...state,
        {
          text: action.text,
          completed: false
        }
      ]
    case 'COMPLETE_TODO':
      return state.map((todo, index) => {
        if (index === action.index) {
          return Object.assign({}, todo, {
            completed: true
          })
        }
        return todo
      })
    default:
      return state
  }
}

import { combineReducers, createStore } from 'redux'
let reducer = combineReducers({ visibilityFilter, todos })
let store = createStore(reducer)

react element-ui

npm install element-react –save

npm install element-theme-default –save

import {Button} from 'element-react';
import 'element-theme-default';

render() {
    return (
        <div>
            <Button className="">kokoko</Button>
            <Button>kokoko</Button>
        </div>
    )
}

react ant-design

import React, {Component} from "react";

import {Row, Col} from "antd";
import 'antd/dist/antd.css'
import {Button} from "@ant-design/icons/lib";

import TopBar from "./TopBar";
import LeftSideBar from "./LeftSideBar";
import Main from "./Main";
import './index.css'

export default class Home extends Component {
    render() {
        return (
            <div>
                <TopBar/>
                <Row>
                    <Col span={4}><LeftSideBar/></Col>
                    <Col span={20}><Main/></Col>
                </Row>
            </div>
        )
    }
}

Tips

react-router url 更新但页面不更新

console Error:Uncaught Could not find router reducer in state tree, it must be mounted under “router”

  • 由于history的最新版不兼容connect-react-router导致。
  • history的最新版为5.0,而connect-react-router使用的history版本为4.7,从而导致reducer执行错误。

参考文章:https://blog.csdn.net/yehuozhili/article/details/107193780

react-router 和 react-router-dom 的区别

react-router v4,我称之为“第四代 react-router”,react-routerreact-router-dom的区别是什么呢?

为什么有时候我们看到如下的写法:

写法1:

import {Switch, Route, Router, HashHistory, Link} from 'react-router-dom';

写法2:

import {Switch, Route, Router} from 'react-router';
import {HashHistory, Link} from 'react-router-dom';

先简单说下各自的功能:

  • react-router: 实现了路由的核心功能
  • react-router-dom: 基于react-router,加入了在浏览器运行环境下的一些功能,例如:Link组件,会渲染一个a标签,Link组件源码a标签行; BrowserRouterHashRouter 组件,前者使用pushStatepopState事件构建路由,后者使用window.location.hashhashchange事件构建路由。
  • react-router-native: 基于react-router,类似react-router-dom,加入了react-native运行环境下的一些功能。

从源码层面来说明:

首先看react-router-dom中的Switch组件的源码

"use strict";
require("./warnAboutDeprecatedCJSRequire")("Switch");
module.exports = require("./index.js").Switch;

只是从react-router中导入Switch组件,然后重新导出而已。

查看其他模块的源码,Route组件的源码Router组件的源码…和Switch一样,都是从react-router中导入了相应的组件,重新导出而已,并没有对实现做什么特殊处理。

结论:

  1. react-router-dom依赖react-router,所以我们使用npm安装依赖的时候,只需要安装相应环境下的库即可,不用再显式安装react-router。基于浏览器环境的开发,只需要安装react-router-dom;基于react-native环境的开发,只需要安装react-router-nativenpm会自动解析react-router-dom包中package.json的依赖并安装。

    react-router-dompackage.json依赖:

    "dependencies": {
      "history": "^4.7.2",
      "invariant": "^2.2.2",
      "loose-envify": "^1.3.1",
      "prop-types": "^15.5.4",
      "react-router": "^4.2.0",
      "warning": "^3.0.0"
    }

    安装了react-router-domnpm会解析并安装上述依赖包。可以看到,其中包括react-router

  2. 回到最开始的写法。基于浏览器环境的开发,写法1就可以了。


React 学习笔记
http://lpxz.work/posts/44509/
作者
LPxz
发布于
2021年12月29日
许可协议