React笔记

该笔记为学习B站尚硅谷React课程所记录。部分截图来源于视频。

React特点

  1. 组件化
  2. 虚拟DOM
  3. 声明式

Babel

  1. ES6 ==> ES5
  2. jsx==>js
  3. tsx==>ts

为什么使用jsx而不是js

创建虚拟DOM时,jsx可以直接写标签,而js只能用React.creatElement(),当有标签嵌套时,js将一直嵌套,非常复杂。jsx相当于js语法糖。

jsx:
在这里插入图片描述
js:
在这里插入图片描述

JSX语法

1. 虚拟DOM
创建虚拟DOM时,直接使用html标签而不是字符串,不能加引号

2. { }
引入js表达式时,需要用{ }括起来

<p className="count-number">
      In {props.title} page, You clicked <span>{count}</span> times
</p>

3. className
在引入css样式时,不使用class,而是className

<div className="hook-example">
    <div>Hook Example</div>
    <p className="count-number">
        In {props.title} page, You clicked <span>{count}</span> times
    </p>
    <button
        onClick={() => {
            setCount(count + 1);
        }}
    >
        Click me
    </button>
</div>
  1. 内联style样式{{ }}
<span style={{color:'red'}}></span>

5. 标签首字母
小写,找html对应标签,没有则报错;
大写,react渲染对应标签,没有则报错

组件

函数式组件

适用于简单组件
在这里插入图片描述
类式组件

适用于复杂组件(使用state的组件)
在这里插入图片描述

React.Component属性

state
在这里插入图片描述
setState

修改状态需要使用setState(),而不能直接对this.state赋值;对state的更新是合并,而非替换;每次更新state都会调用一次render()

this

容易犯错

class Weather extends React.Component{

    constructor(props){
        super(props);
        this.state = {isHot:true};
    }

    render(){
        return <span onClick={this.changeWeather}>{this.state.isHot ? Hot:Cold}</span>;
    }

    changeWeather(){
        const isHot = this.state.isHot;
        this.setState({isHot:!isHot});
    }
}

问题:点击文字并不会改变
this:undefined

这是因为将函数changeWeather赋值给了onClick,并不是对象在调用这个函数;并且因为使用strict模式,this类型是undefined,而不是window

解决办法1:

this.changeWeather = this.changeWeather.bind(this);

在这里插入图片描述

在这里插入图片描述
解决方案2:

changeWeather = ()=>{
        const isHot = this.state.isHot;
        this.setState({isHot:!isHot});
}

箭头函数中的this默认指向其所在函数作用域链

Props

用于存储外部传进来的数据,从而动态渲染页面;props是readonly类型

展开运算符…

利用prefix…可以将一个数组展开

let arr = [1,2,3,4,5]
console.log(...arr)

> 1, 2, 3, 4, 5

还可以合并两个数组

let arr1 = [1,2,3,4,5]
let arr2 = [6,7,8,9]
console.log(...arr1, ...arr2)

> 1, 2, 3, 4, 5, 6, 7, 8, 9

而对于对象来说,…可以将对象进行深拷贝

let person = new Person("Mark")
let referPerson = person
let clonePerson = {...person}
person.name = "Tom"
console.log(referPerson.name)
console.log(clonePerson.name)

> Tom
> Mark

也可以对对象进行扩展

let person = new Person("Mark")
let extendPerson = {...person, sex:'male'}
console.log(extendPerson)

>{name:'Mark', sex:'male'}

在React当中,当需要传递的数据信息很多时,我们往往将这些数据封装成一个对象来进行传递,并且在传递的时候我们采用这样的方式:

<MyComponent person={...person} />

这里采用的是{ }的方式,但并不是将person拷贝一份,因为这里的{ }是jsx中的取值运算符;因为在React+Babel中,对象是可以展开的,所以这里是将person展开,并不是复制,但仅限于在标签中使用

props类型限制

static prototype = {
    name: PropTypes.string.isRequired,
    age: PropTypes.number,
}

注意:如果要限制类型为函数,使用PropTypes.func,而不是PropTypes.function

props默认值设置

static defaultProps = {
    age: 18,
    sex: 'male'
}

函数式组件的props

通过函数参数传递props

function Weather(props){
    return (
        <ul>
            <li>this.props.name</li>
            <li>this.props.sex</li>
        </ul>
    )
}
ReactDOM.render(<Weather name='Mark' sex='male'/>, document.getElementById('root'))

函数式组件使用staterefs可以通过Hooks实现

Refs

在这里插入图片描述
javascript中,我们通常使用document.getElementById来引用某个DOM节点;而在React中,我们使用Refs完成类似的功能

字符串Refs

<input ref="myInput"/>
const input = this.refs.myInput

但是这种方式存在效率问题,官方已经不推荐使用
在这里插入图片描述
回调Refs
在这里插入图片描述
内联回调函数

render() {
      return (
        <div>
          <input
            type="text"
            ref={element => {this.textInput = element;}}
          />
        </div>
      );
    }

回调次数问题
在这里插入图片描述createRef
在这里插入图片描述
勿过度使用 Refs

你可能首先会想到使用 refs 在你的 app 中“让事情发生”。如果是这种情况,请花一点时间,认真再考虑一下 state 属性应该被安排在哪个组件层中。通常你会想明白,让更高的组件层级拥有这个 state,是更恰当的。查看 状态提升 以获取更多有关示例。

将 DOM Refs 暴露给父组件Ref转发在这里插入图片描述

事件处理

  1. React中的事件处理函数名都为onXxx,而原生组件的事件函数名为onxxx

    React对原生DOM事件进行了自定义,将事件委托给组件的最外层元素
    
  2. event.target可以得到发生事件的DOM元素

非受控组件

采用refs对节点进行操作

受控组件

通过事件处理将数据保存到state,通过state进行数据渲染

高阶函数

  • 函数的参数是一个函数
  • 函数的返回值是一个参数

常见高阶函数:PromisesetTimeoutarr.map

函数的柯里化

Curry:只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数

const sum = (a)=>{
    return (b)=>{
        return (c)=>{
            return a+b+c
        }
    }
}
const res = sum(1)(2)(3)
console.log(res)

> 6

生命周期

挂载与卸载

mount

将一个组件挂载到一个容器中

ReactDOM.render(<Demo />, document.getElementById('root'))

unmount

将一个组件从容器中卸载,例如:删掉点击某个按钮关闭某个弹窗

ReactDOM.unmountComponentAtNode(document.getElementById('root'))

生命周期流程(旧)

在这里插入图片描述
componentWillMount

  • 组件挂载之前执行一次
  • 应用
    • 开启定时器
    • 发送网络请求
    • 订阅消息

componentDidMount

  • 组件挂载之后执行一次
  • 应用
    • 关闭计时器
    • 取消订阅

render

  • 组件挂载时调用一次
  • 组件每次更新时调用一次
    • setState
      更新组件状态
    • forceUpdate
      强制更新组件
    • 父组件render
      父组件更新组件

componentWillUnmount

  • 组件卸载之前执行一次

shouldComponentUpdate

  • 组件state更新后执行一次
  • 默认return true
  • return false 则不沿流程执行下去

componentWillUpdate

  • 组件更新之前执行一次

componentDidUpdate

  • 组件更新之后执行一次

componentWillReceiveProps

  • 当父组件的state被当作子组件的props传递时,父组件更新时,子组件会再次接收props,该函数在子组件接收props前调用一次
  • 挂载时并不会调用
render(){
    return (
        <div>
            <Son name={this.state.name}/>
        </div>
    )
}
生命周期流程(新)

在这里插入图片描述
改变:

  • 移除了componentWillMountcomponentWillUpdatecomponentWillReceiveProps
    在这里插入图片描述

  • 新增了getDerivedStateFromPropsgetSnapshotBeforeUpdate
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

Diffing算法

状态数据发生变化时,react根据新数据生成新的虚拟DOM,与旧DOM进行Diffing比较,比较的最小粒度为标签

  • 旧虚拟DOM中找到与新虚拟DOM相同的key
    • 内容不同,生成新DOM节点替换旧的
    • 内容相同,不替换
  • 旧虚拟DOM中没有相同的key
    • 生成新DOM渲染到页面
Key

唯一标识某个组件

index

index赋值给key,非顺序操作元素时:

  • 每个元素的index都将改变,从而导致对应的key发生改变,导致diffing算法在比较时将重新渲染所有元素对应的组件,效率大大降低
  • 当该组件存在子组件、且子组件状态不发生变化时,diffing算法会保留子组件DOM节点,重新渲染父组件DOM节点,导致父子组件匹配错误

因此,如果列表元素存在唯一标识,最好用该唯一标识作为key

create-react-app

核心技术栈:

  • react
  • es6
  • webpack
  • eslint
创建项目并启动

在这里插入图片描述

  1. 安装create-react-app
    npm install -g create-react-app

  2. 创建项目
    cd desktop
    create-react-app react-hello

  3. 启动项目
    cd react-hello
    npm start

项目文件结构

在这里插入图片描述

react-hello
├─node_modules   依赖
├─src
│	├─App.css	组件App样式
│	├─App.js	组件App
│	├─App.test.js	组件App测试文件
│	├─index.css	主页样式
│	├─index.js	主页
│	├─logo.svg	react logo
│	├─reportWebVitals.js	网页性能配置
│	└─setupTests.js	测试配置文件
└─public    静态资源
│	├─favicon.ico	标签图标
│	├─index.html	主页面
│	├─logo192.png	图片size=192
│	├─logo512.png	图片size=512
│	├─manifest.json	加壳配置文件
│	└─robots.txt	爬虫配置文件
├─package.json	项目配置文件
└─yarn.lock	锁定版本号

界面组件化

  1. 拆分界面,按照合适的粒度划分组件
  2. 实现静态组件
  3. 实现动态组件

React Ajax

  • react只关注界面,没有实现ajax
  • ajax是必要的,与后台传送json数据
  • 集成第三方ajax
ajax库
  • jQuery
    jQuery太重,性能不高;react中的虚拟DOM就是为了优化性能
  • axios
    轻量级
    封装XmlHttpRequest请求的ajax
    promise风格
    能在浏览器端与node服务器端使用
配置代理
消息订阅与发布

PubSubjs

react-router

  • SPA (Single Page Application)

    • 整个应用只有一个完整页面,多个组件
    • 点击不刷新页面,制作局部更新
    • 数据通过Ajax请求获取,在前端异步展现
  • 路由

    • 路由是一个映射关系key-value
    • key是一个路径,value是一个组件
安装react-router-dom

命令行输入yarn add react-router-dom

基本用法

Router
BrowserRouter
HashRouter
Route
Link

路由组件
  • 写法
  • 存放位置
  • props
其他组件

NavLink
this.props.children
Switch
默认情况下,匹配路由将比较所有注册的项,这将带来两个问题:

  • 多个组件注册同一个路由,将同时显示多个组件
  • 注册的组件数量大,效率低下

在外面包裹Switch组件标签可以使得匹配第一个路由后停止匹配

Redirect

模糊匹配与严格匹配
嵌套路由
params参数
search参数

querystring

state参数
replace以及push
编程式路由
withRouter

加工一般组件,添加路由组件的三个特有属性historylocationmatch

import {withRouter} from 'react-router-dom'
...
export default withRouter(Component)

React-UI组件库

Ant-Design

安装Ant-Design
引入样式
按需引入样式
自定义主题

Redux

Mobx

LazyLoad

Hooks

Axios

json-server

https://github.com/typicode/json-server 快速搭建REST API的工具包

安装与启动

  1. 安装JSON Server
    npm install -g json-server

  2. 创建一个 db.json 文件, 用下面的数据

    {
      "posts": [
        { "id": 1, "title": "json-server", "author": "typicode" }
      ],
      "comments": [
        { "id": 1, "body": "some comment", "postId": 1 }
      ],
      "profile": { "name": "typicode" }
    }
    
  3. 启动JSON Server
    json-server --watch db.json
    在这里插入图片描述

安装 Axios

在这里插入图片描述

基本用法

在这里插入图片描述
在这里插入图片描述

请求配置信息
{
  url: '/user',
  method: 'get', // default
  baseURL: 'https://some-domain.com/api/',
  transformRequest: [function (data, headers) {
    return data;
  }],
  transformResponse: [function (data) {
    return data;
  }],
  headers: {'X-Requested-With': 'XMLHttpRequest'},
  params: {
    ID: 12345
  },
  paramsSerializer: function (params) {
    return Qs.stringify(params, {arrayFormat: 'brackets'})
  },
  data: {
    firstName: 'Fred'
  },
  data: 'Country=Brasil&City=Belo Horizonte',
  timeout: 1000, // default is `0` (no timeout)
  withCredentials: false, // default
  adapter: function (config) {
  },
  auth: {
    username: 'janedoe',
    password: 's00pers3cret'
  },
  responseType: 'json', // default
  responseEncoding: 'utf8', // default
  xsrfCookieName: 'XSRF-TOKEN', // default
  xsrfHeaderName: 'X-XSRF-TOKEN', // default
  onUploadProgress: function (progressEvent) {
    // Do whatever you want with the native progress event
  },
  onDownloadProgress: function (progressEvent) {
    // Do whatever you want with the native progress event
  },
  maxContentLength: 2000,
  maxBodyLength: 2000,
  validateStatus: function (status) {
    return status >= 200 && status < 300; // default
  },
  maxRedirects: 5, // default
  socketPath: null, // default
  httpAgent: new http.Agent({ keepAlive: true }),
  httpsAgent: new https.Agent({ keepAlive: true }),
  proxy: {
    protocol: 'https',
    host: '127.0.0.1',
    port: 9000,
    auth: {
      username: 'mikeymike',
      password: 'rapunz3l'
    }
  },
  cancelToken: new CancelToken(function (cancel) {
  }),
  signal: new AbortController().signal,
  decompress: true // default
  insecureHTTPParser: undefined // default
  transitional: {
    silentJSONParsing: true, 
    forcedJSONParsing: true,
    clarifyTimeoutError: false,
  }
}

默认配置

axios.default.method='GET'

创建实例对象与发送请求

Axios拦截器

Promise

  • PromiseES6提出的对于异步编程的新解决方案(旧方案为回调函数)
  • Promise是一个构造函数
  • promise对象用来封装一个异步操作,并获取操作的状态值
异步编程
  • fs 文件操作

    require('fs').readFile('./index.html', (err, data)=>{})

  • 数据库操作

  • Ajax
    $.get('/server', (data)=>{})

  • 定时器
    setTimeout(()=>{}, 2000)

Promise特点
  • 支持链式调用,避免回调地狱
  • 指定回调函数的方式更灵活
quickstart
class PromiseTest extends React.Component{

    handleClick(){
        const p = new Promise((resolve, reject)=>{
            setTimeout(()=>{
                let num = 1
                if(num>0){
                    resolve(num)
                }
                else{
                    reject()
                }
            }, 1000)
        })
        p.then(()=>{
            alert('more than 0')
        }, ()=>{
            alert('less than 0')
        })
    }

    render(){
        return (
            <button onClick={this.handleClick}>点击</button>
        )
    }
}
util.promisify

在这里插入图片描述

PromiseState
  • pending
  • resolved/fullfilled
  • rejected

若成功;pending ==> resolved;若失败,pending ==> rejected

PromiseResult
  • value
  • reason

若成功;solve(value);若失败,reject(reason). 通过调用promise.then()promise.catch()result进行处理

Promise Functions
  • Promise.resolve()
    参数为非promise对象,返回一个成功promise对象,PromiseResult为参数
    参数为promise对象,根据该对象执行器的返回结果判断返回成功还是失败

  • Promise.reject()
    无论参数是否为promise对象,都返回失败的promise对象

  • Promise.all()
    参数为promises,一个promise对象数组,只有数组中所有promise都是成功的,才返回成功promiseresult为所有成功对象的result;否则返回失败promiseresult为所有失败对象的result

  • Promise.race()
    参数为promises,一个promise对象数组,最先改变状态的promise对象的结果为最终结果

     Tips:promise中的excutor是同步执行的
    
改变promise状态
  • pending ==> resolved
    调用resolve()
  • pending ==> rejected
    调用reject()throw 异常
常见问题
  1. 给同一个promise指定多个成功/失败回调函数,都会执行吗?

    只要promise的pending状态发生了改变,全部回调函数都会执行;状态未改变则不执行
    

在这里插入图片描述

  1. 先改变状态还是先指定回调函数?何时能拿到结果数据?

     都有可能。
     如果promise中执行的是同步任务,或者then()延迟调用,则先改变状态;
     如果执行的是异步任务,则先指定回调函数。
     无论是先改变状态还是先指定回调函数,都是在状态改变后拿到结果。
    
  2. then()返回的promise对象状态是成功还是失败?

     三种情况:
     	抛出异常,失败
     	return一个非promise对象,成功且结果为返回值
     	return一个promise对象,取决于该返回promise对象的状态
    
异常穿透

promise的链式调用中,无论哪个节点出现异常,只需要在最后的promise使用catch()就能捕获到异常
在这里插入图片描述

中断promise链式调用

返回一个pending状态的promise对象
在这里插入图片描述

Async 函数

返回结果是一个promise对象,PromiseState以及PromiseResult情况如下:

  • 抛出异常,throw 'message'{PromiseState:rejected,PromiseResult:message}
  • 返回一个非promise对象,return 123{PromiseState:fullfilled,PromiseResult:123};没有返回值,{PromiseState:fullfilled,PromiseResult:undefined}
  • 返回一个promise对象,取决于该返回promise对象的PromiseStatePromiseResult
Await 表达式
  • await一定要在async函数中使用;但async函数中可以没有await
  • await后面的变量是一个非promise对象,返回该变量值
  • await后面的变量是一个状态成功的promise对象,返回该对象PromiseResult
  • await后面的变量是一个状态失败的promise对象,通过try-catch捕获失败对象的PromiseResult
    在这里插入图片描述
    在这里插入图片描述
    > Error
async await 简化代码

未使用promise
在这里插入图片描述
使用promise
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值