前端之React篇

340 阅读19分钟

一个人只要知道自己去哪里,全世界都会给他让步。

React是一个js库

原理

  • React基于虚拟dom,通过js创建虚拟dom,虚拟dom转换真实dom并挂载到页面 没有在任何一个项目中直接使用标签,而是通过js创建标签

  • 创建虚拟dom :
    react.createElement() jsx => 遇到 < > 解析成虚拟dom ; 遇到 { } 解析成js

  • react 提供了一个挂载真实dom节点在页面的方法 : 虚拟dom转换真实dom并挂载到页面 reactDom.render(要挂载的真实dom节点,挂载的容器id,回调函数)

搭建React项目

1.全局安装脚手架

npm i create-react-app -g

2.创建一个react项目

create-react-app XXX

3.cd进项目进行

cd 项目

4.释放 【(不可逆的)(在没有破坏项目完整性之前去释放)】

npm run eject

5.启动项目【可以在package.json/scripts中自定义指令】

npm run build / npm start

组件

组件的创建【两种】

第一种 :函数式
特点:
function App(props){
    //无this指向,内部this为 undefined
    //无状态,没有state
	//内部访问props通过参数,props是函数形参
	//没有生命周期
	//有返回值
    return <div>i am app</div>
}
第二种 :类声明
特点:
class App extends Component{
    // 内部this指向我们的组件实例 
    // 有状态,有state
    // 有生命周期
    // 内部访问props通过this 
    
    state={
        txt:'i am app'
    }

    render(){
        return <div>{txt}</div>
    }
}
export default App

生命周期 :

在这里插入图片描述

区别:

性能方面: 函数组件的性能比类组件的性能要高,因为类组件使用的时候要实例化,而函数组件直接执行函数取返回结果即可 函数组件没有this,没有生命周期,没有状态state, 类组件有this,有生命周期,有状态state。

react的组件关系及组件通信

父子通信 使用props (传string number object)

子父 回调函数 (子组件调用父组件的函数,并通过参数传值)

react受控组件(input表单元素和onchange事件的一个结合)

定义:受状态state控制的表单组件

区分value defaultValue 用法

children用法

用于组件传递标签节点,子组件通过this.props.children获取

获取真实dom节点 ref&refs

ref用来命名节点 this.refs 获取节点

给组件设置默认props

组件.defaultProps = {

定义的变量key:变量的默认值

}

react 跨级传值

组件关系

父子 属性-props接收 子父 回调函数 同级 redux 跨级 redux (createContext)

什么是跨级传值 ? 嵌套关系的组件想要进行变量或者方法的传递

react中跨级传值使用 createContext

import React, { Component ,createContext} from 'react';
const {Provider,Consumer} = createContext();

//变量传递的一方 使用Provider
class GrandFather extends Component{
    state={
        name:'顺溜'
    }

    setName = name => this.setState({name})
    
    render(){
        return <Provider value={{name:this.state.name, setName:this.setName }}>
            <div className="grandFather">
                爷爷
                <TwoFather></TwoFather>
            </div>
        </Provider> 
    }
}

//接受变量的一方 使用Consumer
class Son extends Component{
    
    state={
        sonName:''
    }
    render(){
        const {sonName} = this.state
        return <Consumer>
               {
                   store =>{
                        return  <div className="son">
                        我的名字叫{store.name}
                        <input type="text" value={sonName} onChange={ev =>this.setState({sonName:ev.target.value})}/>
                        <button onClick={()=>store.setName(sonName)}>改名字</button>
                    </div>
                   }
               }
        </Consumer>
    }
}

react 合成事件

在react中所有元素的事件并不是元素本身去触发,而是通过事件冒泡到document身上

react中的事件类型

在这里插入图片描述

React面试题

  • state用于内部,不与外部进行交互

  • props单项数据流,内部不改变

  • react.js == > React核心库

(组件的)状态(state)和属性(props)之间有何不同

  • State 是一种数据结构,用于组件挂载时所需数据的默认值。State 可能会随着时间的推移而发生突变,但多数时候是作为用户事件行为的结果。

  • Props(properties 的简写)则是组件的配置。props 由父组件传递给子组件,并且就子组件而言,props 是不可变的(immutable)。组件不能改变自身的 props,但是可以把其子组件的 props 放在一起(统一管理)。Props 也不仅仅是数据–回调函数也可以通过 props 传递。

react中的函数式组件及类组件的选择?

  • 组件内部需要包含内部状态state或者需要用到生命周期时考虑类组件,其他情况使用函数组件(无状态组件)。

react中受控组件和非受控组件区别?

  • 受控组件:表单元素的数据托管到react组件中,交由react控制。取值通过state。

受控组件

  • 通俗讲就是将状态完全交给父组件来管理,只负责显示

  • 能够控制表单中输入元素的组件被称为受控组件,即每个状态更改都有一个相关联的处理程序。

  • 就是通过input的属性value 来设置可读属性,让内容无法被更改,在通过表单的onchange事件,去更新受控组件内部的内容

  • 非受控组件:表单元素的数据是存放在dom中。取值通过ref。

非受控组件

  • 状态由组件自己管理,父组件只能通过ref来获取他的状态

  • 与受控组件相反,即没有设置value或者设置为null的是一个非受控组件,对于非受控的input组件,用户的输入会直接反映在页面上

调用setState 之后发生了什么?

  • setState执行机制:合并、更新,多次调用,先存入队列,在合适的时机覆盖

  • 多次调用setStateDom更新完毕之后执行,state中的数据不立即修改而是存入队列,与之前state的数据合并,走一遍确认没有其他数据需要更新之后,根据队列中需要修改的数据进行比较,最后覆盖.

  • 调用setstate函数之后,react 会将nextState与当前的组件state进行合并,触发一个调和过程。 在这个过程中react会基于底层diff算法(diff通过key能相对精准的知道哪些位置发生了改变)得到新dom树与老dom树的节点差异, 根据差异,对界面进行最小化重渲染。

React调用setState是同步还是异步

  • setState 只在合成事件和钩子函数中是“异步”的,在原生事件和 setTimeout 中都是同步的。

  • setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果

  • setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次 setState , setState 的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新

React中key的作用?

  • 提高性能、优化

  • diff算法【比较差异】

  • key是一个用于追踪哪些元素被修改或者被移除的标识。

答案一:

key是React中用于注重那些列表中的元素被修改、被添加或者被移除的辅助标识,在日常开发的过程中我呢吧需要保证摸个元素的key在其同级元素中具有唯一性,在React Diff算法中React会借助key值来判断该元素是新创建还是被移动而来的元素,从而减少不必要的元素渲染

答案二:

key(唯一性)的主要作用是减少没必要的diff算法对比,提高diff算法的效率。

备注:对于一个组件或节点来说,只要父节点的状态或者属性发生变化,该组件就会进行diff对比,即使该组件未发生变化,而如果为组件引入key值,就可以在diff对比前先进行校验,判断该组件是否需要diff对比,即使需要diff对比,也可以判断该组件是直接更新还是销毁或新建

React diff原理

  • 把树形结构按照层级分解,只比较同级元素。

  • 给列表结构的每个单元添加唯一的 key 属性,方便比较。

  • React 只会匹配相同 class 的 component(这里面的 class 指的是组件的名字)

  • 合并操作,调用 component 的 setState 方法的时候, React 将其标记为 dirty.到每一个事件循环结束, React 检查所有标记 dirty 的 component 重新绘制.

  • 选择性子树渲染。开发人员可以重写 shouldComponentUpdate 提高 diff 的性能。

虚拟Dom

  • 普通的对象【Object类型的实例】

  • 能够描述真实Dom的特点(不是所有都是虚拟Dom) <tagName>、props、children

  • 真实Dom消耗性能更大

为什么虚拟DOM会提高性能?

虚拟 dom 相当于在 js 和真实 dom 中间加了一个缓存,利用 dom diff 算法避免了没有必要的 dom 操作,从而提高性能。

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

JSX语法 【注:jsx不是js,不能运行】?

  • jsx需要被babel转码,转化成VDom

  • 用标签写虚拟Dom转化为React.createElement( )方法的调用

语法

  • 遇到 { } 解析成 JS

  • 遇到< >解析成 虚拟Dom

React新增生命周期?

  • getDerivedStateFromProps,getSnapshotBeforeUpdate

  • getDerivedStateFromProps:从属性中导出【用于state与props发送耦合时】

React路由跳转与a标签link的区别?

  • <a href="/"></a>是前台向服务端发起请求,寻求资源

  • 路由跳转则不会发送请求,而是浏览器url地址栏发生改变,根据路径的变化切换相应的组件

单页面刷新之后,视图如何正确显示(路由向后台发送请求)?

页面被刷新后会向后台发送请求,而后台没有对应的接口,为了在页面的正常展示,后台需要做相应的处理

  • 前端路由在后台路由中不一定存在

  • 后台检测到是前端单页面的路由,响应单页面文件,并更新视图状态 最后的项目都要打包成js文件,引入页面,后台通过前端路由返回对应的组件 具体就是根据url路径返回<div id="root"></div>,导入相应的js文件

React如何提高组件性能?

  • key

  • PureComponent 详解

  • shouldComponentUpdate

Redux的原理【机制】

  • 提供存储的机制,内存空间:2kB 状态管理仓库,管理组件状态 Store ==> 存储 getState ==> 查看仓库状态【调用getState ,传递action指导如何修改,触发reducer修改数据,返回newState(最新的状态),最终获取state的最新数据】 dispatch ==> 修改
  • 不用Redux也可更新数据 store.subscribe详解 监听,当仓库状态发生改变时就会执行,调用setState({ }),实现实时数据更新

React通信

  • 父子
    父组件:指明key值,value值 子组件:props接受key值,value值
  • 子父
    子组件:回调函数 父组件:接受参数
  • Redux仓库
  • Context Provider Consumer
  • 发布订阅模式【先订阅再发布】

React生命周期

1.挂载和卸载的过程(实例期)

1.1 constructor()

完成了React数据的初始化

1.2 componentWillMount()

数据初始化之后,但是我渲染DOM时

1.3 render()
render函数会插入jsx生成的dom结构,react会生成一份虚拟dom树,在每一次组件更新时,在此react会通过其diff算法比较更新前后的新旧DOM树,比较以后,找到最小的有差异的DOM节点,并重新渲染。
1.4 componentDidMount()

组件第一次渲染完成,此时DOM节点已经生成,可以在这里返送Ajax请求,返回数据调用setState后组件会重新渲染

2.更新过程(存在期)

2.1 componentWillReceiveProps()

接受一个参数(nextProps)在接受父组件改变之后的props时,对比nextProps和this.props,将nextProps的state作为当前组件的state,从而重新渲染组件

2.2 shouldComponentUpdate(nextProps,nextState)

主要用于心梗优化,是控制组件是否重新渲染的生命周期,因为react父组件的重新渲染会导致所有子组件重新渲染,这个时候我们不需要所有子组件的重新渲染,因此在次生命周期中return false就可以阻止组件更新

2.3 componentWillUpdate(nextProps,nextState)

shouldComponentUpdate返回true以后,会进入重新渲染的流程,在此生命周期同样可以拿到nextProps和nextState

2.4 render()
render函数会插入jsx生成的dom结构,react会生成一份虚拟dom树,在每一次组件更新时,在此react会通过其diff算法比较更新前后的新旧DOM树,比较以后,找到最小的有差异的DOM节点,并重新渲染。
2.5 componentDidUpdate(prevProps,prevState)

​ 组件更新完毕后,react只会在第一次初始化成功会进入componentDidmount,之后每次重新渲染后都会进入这个生命周期,这里可以拿到prevProps和prevState,即更新前的props和state

3.销毁期componentWillUnmount ()

在此处完成组件的卸载和数据的销毁

删除的生命周期

  • componentwillmount

  • componentWillReceiveprops

  • componentWillUpdate

新增的生命周期

getDerivedStateFromProps(nextProps,Prevstate)

代替了componentWillReceiveProps,老版本中的componentWillReceiveProps()方法判断前后两个 props 是否相同,如果不同再将新的 props 更新到相应的state 上去。这样做一来会破坏 state 数据的单一数据源,导致组件状态变得不可预测,另一方面也会增加组件的重绘次数

componentWillReceiveProps 中,我们一般会做以下两件事,一是根据 props 来更新 state,二是触发一些回调,如动画或页面跳转等。

而在新版本中,官方将更新 state 与触发回调重新分配到了 getDerivedStateFromPropscomponentDidUpdate 中,使得组件整体的更新逻辑更为清晰。而且在 getDerivedStateFromProps 中还禁止了组件去访问 this.props,强制让开发者去比较 nextPropsprevState 中的值,以确保当开发者用到 getDerivedStateFromProps 这个生命周期函数时,就是在根据当前的 props 来更新组件的 state,而不是去做其他一些让组件自身状态变得更加不可预测的事情

getSnapshotBeforeUpdate(prevProps, prevState)

代替componentwillUpdate, 常见的 componentWillUpdate 的用例是在组件更新前,读取当前某个 DOM 元素的状态,并在 componentDidUpdate 中进行相应的处理

两者区别在于 在 React 开启异步渲染模式后,在 render 阶段读取到的 DOM 元素状态并不总是和 commit 阶段相同,这就导致在 componentDidUpdate 中使用 componentWillUpdate 中读取到的 DOM 元素状态是不安全的,因为这时的值很有可能已经失效了 。

getSnapshotBeforeUpdate 会在最终的 render 之前被调用,也就是说在 getSnapshotBeforeUpdate 中读取到的 DOM 元素状态是可以保证与 componentDidUpdate 中一致的

React函数式组件和类组件的区别及其特点

区别

函数组件的性能比类组件的性能要高,因为类组件使用的时候要实例化,而函数组件直接执行函数取返回结果即可。为了提高性能,尽量使用函数组件。

函数组件没有this,没有生命周期,没有状态state

函数式组件

函数式组件只考虑负责UI的渲染,没有自身的状态,是一个纯函数,他的输出只有参数props决定,不受其他因素的影响

总结

无论是使用函数或是类来声明一个组件,它决不能修改它自己的 props

所有 React 组件都必须是纯函数,并禁止修改其自身 props

React是单项数据流,父组件改变了属性,那么子组件视图会更新。

属性 props 是外界传递过来的,状态 state 是组件本身的,状态可以在组件中任意修改

组件的属性和状态改变都会更新视图

了解react的hooks的特性

hooks的优点

  • 更容易复用代码

  • 函数式编程的代码风格

  • 代码量更少

hooks的缺点

  • 响应式的useEffect

  • 状态不同步

hooks为函数组件提供了状态,也支持在函数组件中进行数据获取、订阅事件解绑事件等等。下面先介绍几个最基本的hook作为基础知识

1) useState

通过useState为组件提供状态。这是一个简单的useState例子,计数器,useState的参数是state的初始值,他只有在组件第一次渲染的时候会生效,他的返回值是一个数组,第一个是state,第二个是设置state的函数

2) useEffect

副作用。通常在副作用中进行ajax请求,事件的绑定与解绑,设置定时器与清除等等。这是一个简单的useEffect的例子,useEffect基本用法,useEffect第一个参数是一个回调函数,在里面进行业务逻辑代码的书写;第二个参数是依赖项数组,如果数组中的依赖发生变化,那么该副作用就会重新执行,如果不设置第二个参数,那么当该组件每渲染一次,副作用就会执行一次;当然如果设置空数组,那么该副作用只会在组件初次渲染时执行一次。

注意,有时我们会需要清除副作用,例如,定时器,useEffect的回调函数接受一个返回值,这个返回值是一个函数,在这个函数中我们可以执行清除副作用操作,上例中,如果不清除定时器,那么副作用每执行一次,就会产生一个新的定时器,造成内存溢出

3)useCallback

用于缓存函数,第一个参数为要缓存的函数,第二个参数为依赖项数组,如果依赖发生了变化,那么就会生成一个新的函数;否则当组件重新渲染时,不会重新定义这个函数,而是会取缓存

4)useMemo

用于缓存函数的返回值,第一个参数为要缓存的函数(注意实际被缓存的是函数被执行过后的值),第二个参数为依赖项数组,如果依赖发生了变化,那么就会重新执行这个函数,得到新的返回值;否则当组件重新渲染时,不会重新执行这个函数,而是直接取被缓存的该函数的返回值

extends继承PureComponent和Component的区别

React.PureComponent它用当前与之前 props 和 state 的浅比较覆写了 shouldComponentUpdate() 的实现

	简单来说,就是PureComponent简单实现了shouldComponentUpdate()的功能

当然,如果你的数据结构比较复杂就不行了

react-router的原理

react-router主要的依赖就是独立的第三方库history,主要分为:hashHistory、beowserHistory、memoryHistory三中类型

const history = {
    length: globalHistory.length,
    action: "props",
    location: initalLocation,
    createHref,
    push, // 改变location
    replace,
    go,
    goBack,
    goForward,
    block,
    listen //监听路由变化
}

hashHistory:通常用于老版本的浏览器,主要是通过hash来实现

browserHistory:通常用于高版本的浏览器,通过HTML5中的history来实现

// 页面跳转的实现

function push() {
    createKey(); // 创建location的key,用于唯一标识该location,是随机生成的
    if (BrowserHistory) {
        globalHistory.pushState({ key, state }, null, href);
    } else if (HashHistory) {
        window.location.hash = path;
    }
    // 上报listener,更新state
}

function replace() {
    createKey();
    if (BrowserHistory) {
        globalHistory.replaceState( { key, state }, null, href);
    } else if (HashHistory) {
        window.location.replace(window.location.href.slice(0, hashIndex >= 0 ? hashIndex : 0) + "#" path);
    }
    // 上报listener,更新state
}


// 页面回退的实现
function pop() {
    if (BrowserHistory) {
        window.addEventListener("popstate", routerChange);
    } else if (HashHistory) {
        window.addEventListener("hashChange", routerChange);
    }
}

function routerChange() {
    const location = getDOMLocation(); //获取location
    transitionManger.confirmTransitionTo(location,  callback = () => {      // 路由切换
        transitionManager.notifyListeners();  // 上报listener
    })
}

memoryHistory:node环境中,主要存储在momory中

React如何优化

  • shouldComponentUpdate生命周期避免重复渲染

  • 组件尽可能的拆分,解耦

  • 不要滥用props

  • ReactDomServer 进行服务端渲染组件

23、Vue和React的区别

本质区别:React是在组件JS代码中,通过原生JS实现模板中的常见语法,比如插值,条件,循环等,都是通过JS语法实现的,更加纯粹更加原生。而Vue是在和组件JS代码分离的单独的模板中,通过指令来实现的,比如条件语句就需要 v-if 来实现对这一点,这样的做法显得有些独特,会把HTML弄得很乱。

1. 虚拟DOM

  • Vue:计算出Dom的差异,在渲染过程中跟踪每个组件的依赖关系,不会重新渲染整个组件树

  • React:当应用的状态改变时,重新渲染全部子组件,可以通过shouldComponentUpdate生命周期进行优化

2. 模板和jsx

  • Vue:具有单文件组件,可以吧html,css,js卸载一个vue文件里------是MVVM框架

  • React:依赖于jsx,在js中创建DOM------是视图层框架

3. 数据绑定

  • vue是响应式的数据双向绑定,而react是单向数据流,没有双向绑定

4. 应用

  • vue的语法较为简单,适用于小型项目创建,而react更适用于Web端和原生App的开发,侧重于大型应用。

5. 组件写法不一样

  • react推荐的做法是JSX+inline style,也就是把HTML和CSS全都写进javaScript了

6. state对象

  • state对象在react应用中是不可变的,需要使用setState方法更新状态

  • 在vue中,state对象不是必须的,数据有data属性在vue对象中管理

React高阶组件【本质为函数】

  • 接受一个组件,并返回一个新的组件
  • withRouter路由拦截
  • 如果一个函数操作其他函数,即将其他函数作为参数或将函数作为返回值,将其称为高阶函数。高阶组件(high-order component)类似于高阶函数,接收 React 组件作为输入,输出一个新的 React 组件。高阶组件让代码更具有复用性、逻辑性与抽象特征。可以对 render 方法作劫持,也可以控制 props 与 state。

高阶组件的作用

a. 属性代理(Props Proxy)

可以说是对组件的包裹,在包裹的过程中对被包裹的组件做了点什么(props的处理,把基础组件和其他元素组合),然后返回,这就成了一个高阶组件

b. 反向继承( Inheritance Inversion )

可以理解成是组装,和属性代理不同的是,反向继承是继承自基础组件,所有很自然,它可以直接获取基础组件的props,操作基础组件的state。可以通过 反向继承的形式,配合compose将携带不同功能模块的高阶组件组装到基础组件上,加强基础组件