一个人只要知道自己去哪里,全世界都会给他让步。
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仓库
ContextProviderConsumer- 发布订阅模式【先订阅再发布】
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 与触发回调重新分配到了 getDerivedStateFromProps 与 componentDidUpdate 中,使得组件整体的更新逻辑更为清晰。而且在 getDerivedStateFromProps 中还禁止了组件去访问 this.props,强制让开发者去比较 nextProps 与 prevState 中的值,以确保当开发者用到 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将携带不同功能模块的高阶组件组装到基础组件上,加强基础组件