redux
中间件的原理的理解,答案如下所示:redux
是一个应用数据流框架,主要是解决了组件间状态共享的问题,原理是集中式管理,主要有三个核心方法,action
,store
,reducer
view
调用 store
的 dispatch
接收 action
传入 store
,reducer
进行 state
操作,view
通过 store
提供的 getState
获取最新的数据state
,对状态的管理更加明确,通过 redux
,流程更加规范了,减少手动编码量,提高了编码效率,同时缺点时当数据更新时有时候组件不需要,但是也要重新绘制,有些影响效率。一般情况下,我们在构建多交互,多数据流的复杂项目应用时才会使用它们redux
中间件,如下所示:
redux-thunk
:处理异步操作redux-saga
:处理异步操作redux-promise
:处理异步操作,actionCreator
的返回值是promise
koa
中间件的执行原理类似,但是不是洋葱型的,而是半个洋葱,因为redux
是单向执行的。当你应用了中间件,在触发一个action
操作的时候,action
操作就会经过先经过中间件,最终再形成dispatch(action)
。一个触发一个action
动作的时候,代码的执行逻辑。thunk
是允许dispatch
一个函数,而不是一个对象thunk
中间件的内部,代码如下所示:function createThunkMiddleware(extraArgument) {
return ({
dispatch, getState }) => next => action => {
// 如果是函数,就执行函数
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
// 如果不是,执行下一个中间件
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
const store = createStore(reducer, preloadedState, enchancer);
// 如果没有中间件,正常触发一个action;
// 如果有中间件的时候 creatStore内部的执行逻辑是这样的
// enchancer 就是你应用的中间件,调用applyMiddleware得到的组合中间件
function applyMiddleware(...middlewares) {
return createStore => (...args) => {
// 该创建的store还是要创建的,只传入了两个参数,没有中间件,得到的是正常的store
const store = createStore(...args)
let dispatch = () => {
throw new Error(
`Dispatching while constructing your middleware is not allowed. ` +
`Other middleware would not be applied to this dispatch.`
)
}
// 把getState、dispatch传给中间件
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
// 下面方法返回来了一个函数数组,中间件被剥离到
// next => {}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// 再执行下面的,中间件就被剥离到
// action => {}
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
// 下面得到的结果是
// 假设中间件为 a b
// a(b(store.dispatch))
return enchancer(createStore)(reducer, preloadedState);
// 结合上面thunk的源码
({
dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState);
}
return next(action);
};
store
的样子,假设中间件为 a b c
,每一个中间件配发了一个原始store
的dispatch
,中间件函数嵌套执行,如下所示
store = {
dispatch,
... ...,
subscribe,
getState
}
store = {
dispatch: a(b((store.dispatch))),
... ...,
subscribe,
getState
}
redux-thunk
就是一个封装函数,允许store.dispatch
一个函数,compsoe
内部,如下所示:// compose本身并不改变函数的执行,将函数组合后又返回了一个函数
function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
redux
中管理,还是共享数据放在 redux
中管理,答案如下所示:不是react native
,所有数据都要放在redux
中管理。如果只是把共用的数据放在redux
中,一个组件中会既有state、props和redux
存储数据,那么当页面出现问题,要从state、props和redux
三个方面检查,开发程序是很快的,但是最费时间的是程序后期的可维护性和代码的可调节性。如果数据都放在redux
中管理,项目出错以后,就只用检查redux
,定位错位很快。不要想着state
中的数据只会供一个组件使用,在项目越来越大的时候,说不准别的组件会需要使用,redux
中可以存储5GB
的数据。所以,能用redux
的时候一定要用redux
,对于后期的维护来说很方便
immutable
,当你把redux
和immutable
这个库结合使用的时候,你整个项目的性能会达到最优,而且非常非常简单。没有数据臃肿的顾虑,你不存在redux
中,你也需要存储在state
或者props
中。
componentWillReceiveProps
的调用时机,答案如下所示:props
传值时触发的函数props
改变的时候才会调用,父组件第一次往子组件传值的时候,不会调用react
性能优化的最佳实践,答案如下所示:PureComponent
,自带shouldcomponentupdate
,是一个浅比较,代码如下所示:class Test extends React.PureComponents {
constructor(props) {
super(props)
}
render() {
return <div>hello</div>
}
}
immutable.js
库的结合,完美的解决react
的性能问题shouldComponentUpdate
钩子对新旧props
和state
进行比较,如果值相同则阻止更新,避免不必要的渲染,或者使用PureReactComponent
替代Component
,其内部已经封装了shouldComponentUpdate
的浅比较逻辑key
属性,以方便React
的diff
算法中对该节点的复用,减少节点的创建和删除操作render
函数中减少类似onClick={() => {doSomething()}}
的写法,每次调用render
函数时均会创建一个新的函数,即使内容没有发生任何变化,也会导致节点没必要的重渲染,建议将函数保存在组件的成员对象中,这样只会创建一次webpack-bundle-analyzer
分析当前页面的依赖包,是否存在不合理性dom
的理解,虚拟dom
会提升代码性能的原因,答案如下所示:dom
就是真实dom
的一个js
对象dom
的比对,真实的dom
节点会有事件,属性,还会有各种各样的方法。所以两个真实dom
的比对会非常耗性能。于是把dom
对象变成js
对象,js
对象就没有dom
对象上乱七八糟的特性了,js
对象就比较快。webpack
中,借助loader
完成的JSX
代码的转化,还是babel
,答案如下所示:babel - preset-react
所去完成的转化setState
后,发生的过程,答案如下所示:setState
函数之后,react
会将传入的参数对象与组件当前的状态合并,然后触发调和过程(Reconciliation)
,以高效方式根据新的状态构建React
元素树并且着手重新渲染整个UI界面React
得到元素树之后,React
会自动计算出新的树与老树的节点差异,然后根据差异对界面进行最小化重渲染,按需渲染,不是全部渲染this.setState({
age: this.state.age + 1
}) // 如果是连续点击一个按钮调用这个setState,会出现数值不是一个一个加上去的,而是会出现一次几个的变化,因为react会把多个setState整合为一个,最后在改变state。
this.setState((prevState) => ({
age: ++ prevState.age
})) // 不管你怎么疯狂的点击按钮,都会一个一个往上加。
setState
是异步的,这个点你在什么时候遇到过坑,答案如下所示:setState
的时候返回传一个函数,同上所示setState
是异步还是同步主要看是谁在调用它,大部分情况下是异步的,小部分情况是同步的React
代理的合成事件中调用,如 onClick、onChange
事件,这些事件都是 React
为了代理原生事件而封装出来的一整套事件机制,我们称之为合成事件setState
的异步不是真的异步,setState
本身的执行过程是同步的,是 React
将多个 setState
进行合并和批量更新,导致其看起来像是异步的在原生事件中调用,代码如下:
class App extends React.Component{
...
componentDidMount(){
document.querySelector('#A').addEventListener('click',()=>{
this.setState({
// 这里的 setState 是同步的
})
})
}
}
在setTimeout()中调用
refs
的作用是什么,你在什么业务场景下使用过refs
,答案如下所示:refs
的作用是操作dom
,访问 DOM
元素或者某个组件实例refs
,展示一个图片,获取图片的宽高,这也是因为react
不能直接操作dom
class Test extends Component {
// 需求:当页面滚动,监听页面滚动的事件
constructor(props) {
super(props);
this.state = {
top: 0
}
this.handleWindowScroll = this.handleWindowScroll.bind(this)
}
handleWindowScroll() {
this.setState(() => ({
top: document.body.scrollTop
}))
}
componentDidMount() {
window.addEventListener('scroll', this.handleWindowScroll)
}
componentWillUnmount() {
window.removeEventListener('scroll', this.handleWindowScroll)
}
render() {
return <div>{
this.state.top}</div>
}
}
ref
是一个函数,好处是什么,答案如下所示:react
在销毁或者重新渲染组件的时候去有效的去清空ref
里面的东西,防止内存泄漏,以后ref
不要用字符串的形式了,要用函数式的写法class Test extends Component {
componentDidMount() {
this.elem
}
render() {
return <div ref={
(div) => {
this.elem = div }}></div> // ref使用的时候最好使用函数
}
}
react
里面不要去使用继承,为什么,设计模式中有这样一句话“组合优于继承”,react
这种组件式的编程,是一种组合类型的设计模式,一定是优于继承的,可维护性是比继承高的多,react
中所有问题都是可以利用组件拼合的方法解决的。hook
解决了这个问题,代码如下所示:<A>
<B>
<C>
<D />
</C>
</B>
</A>
react-redux connect
其实就是一个高阶组件。HOC
是纯函数,没有副作用。纯函数是输入确定,输出就一定确定HTML
中,类似 ,
这样的表单元素会维护自身的状态,并基于用户的输入来更新。当用户提交表单时,前面提到的元素的值将随表单一起被发送。但在 React
中会有些不同,包含表单元素的组件将会在 state
中追踪输入的值,并且每次调用回调函数时,如 onChange
会更新 state
,重新渲染组件。一个输入表单元素,它的值通过 React
的这种方式来控制,这样的元素就被称为"受控元素"input
框,我直接操作dom
,我不让他进行数据的绑定,输入完成点击按钮的时候,我直接通过refs
拿dom
上的内容来进行操作,不是通过数据来控制react
是一个数据驱动的框架,所以数据驱动是react
核心,所以组件都应该被数据控制hooks
,答案如下所示:Hooks
技术,其作用让函数组件变得强大起来,它可以让 react
函数组件也拥有状态,让我们用现有的 JavaScript
技术就能快速上手,让我们获取数据、更改状态是如此的轻松Hook
操作数据状态相比类组件更简洁,这也是函数式编程的魅力Hooks
的内容比较多,比如常用的三个基本 Hook
功能:useState、useEffect、useContext
,以及额外的方法:useRef、useReducer、useMemo、useCallback、useLayoutEffect、useDebugValue
等this
指向问题的解决方法,答案如下所示:bind
绑定this
,这一种方法使用bind
来修改this
的指向,需要注意的是bind
括号内第一个参数是修改this
的,后面可以设置其他参数进行传值,代码如下所示: run(){
alert("第一种方法!")
}
<button onClick={
this.run.bind(this)}>第一种</button>
this
指向,和第一种方法原理一样,只是写的位置不同,代码如下所示:constructor(props) {
super(props);
this.state={
//定义数据
}
this.run = this.run.bind(this);
}
run(){
alert("第二种方法!")
}
<button onClick={
this.run}>第二种</button>
run
方法再等于一个箭头函数,利用箭头函数没有自己的this
指针会继承外层的作用域这个特性,来解决this
指向问题,代码如下所示:run=()=> {
alert("第三种方法!")
}
<button onClick={
this.run}>第三种</button>
run(){
alert("第四种方法!")
}
<button onClick={
()=>this.run()>第四种</button>
props
发生变化以后,函数就会重新执行,React.memo(function Test() { return 123 })
,这样包装,组件就会有shoulComponentUpdate
这样的属性,这样函数式组件的性能一定要不普通组件的性能要好的function Test() {
return <div>123</div>
}
16.在哪个生命周期里发送ajax
,答案如下所示:
componentDidMount
中去发送componentWillMount
在新版本的react
中已经被废弃了,取而代之的是一个getDerivedStateFromProps
这样一个生命周期函数,所以用componentWillMount
不合适ssr
项目中的时候,componentWillMount
要做服务端数据的获取,所以不能被占用ssr
的原理是什么,答案如下所示:SSR
,服务端渲染,React
代码在服务端上运行,直接生成带有数据的 HTML
页面( ajax
请求均在服务器上完成 ),然后直接将该页面返回给客户端,客户端只需解析 HTML
就能展示页面SEO
,因为在后端有完整的 HTML
页面,所以爬虫更容易爬取关键信息HTML
页面即可,这样对于客户端的资源占用更少,尤其是移动端,也可以更省电React
代码由服务端执行并生成完成页面,当外部访问量增多,可能会出现页面加载变慢( 请求阻塞 )等情况,此时可以通过负载均衡策略解决SSR
的项目,如下所示:
SEO
,SSR
就很合适 ( 关于 SEO
,预渲染也能做到 )SSR
可以减少白屏时间react
服务端渲染的流程,如下所示:
cdn
)render
哪个页面,主要用到renderToStringapi
将page
组件转化为html
标记html
标记直接返回客户端,渲染一个静态页面html
,这种情况下,当在客户端重新render
时,如何保证数据一致呢,解决办法是将服务端获取到的数据以字符串的形式返回给客户端,客户端渲染的时候直接以该数据进行渲染,保证数据的一致性,进而保证了ui
的一致性hydrateapi
将html
标记与js
代码重新结合,之后就与服务端完全没关系了,可以当spa
的情况处理react
中实现 SSR
的核心原理,就是虚拟 DOM
的存在redux-saga
的设计思想的理解,以及sideEffects
的理解,答案如下所示:redux
设计思想,web
应用是一个状态机,视图与状态是一一对应的,所有的状态保存在一个对象里面saga
的应用整体思路,使用了Saga
后,react
只负责数据如何展示,redux
来负责数据的状态和绑定数据到react
,而Saga
处理了大部分复杂的业务逻辑app.js
入口文件引入saga
以及reducers
,动态执行saga
语句 middleware.run(sagas)
必须要在store
创建好之后才能执行,在 store
之前执行,程序会报错。rootsage
为所有模块组件的sage
集合,用sage
副作用的all
方法同时并发发出。引入action
组件,发出action
中定义好的action
,用saga
副作用处理函数 takeLatest
(类似于防抖),执行最后一次请求。reducer
文件对需要更改返回state
的action
进行处理redux-saga
就是 redux
的一个中间件,用于更优雅地管理副作用(side effects
),redux-saga
可以理解为一个和 系统交互的 常驻进程,可简单定义为 saga = Worker + Warcher
effect
是一个普通的 javascript
对象,包含一些指令,这些指令最终会被 redux-saga
中间件 解释并执行。在 redux-saga
世界里,所有的 Effect
都必须被 yield
才会执行。原则上来说,所有的 yield
后面也只能跟Effect
,以保证代码的易测性,task
是 generator
方法的执行环境,所有saga
的generator
方法都跑在task
里action
的问题saga.js
中,不再掺杂在action.js
中,保持 action
的简单纯粹,又使得异步操作集中可以被集中处理,对比redux-thunk
redux-saga
提供了丰富的 Effects
,以及 sagas
的机制(所有的 saga
都可以被中断),在处理复杂的异步问题上更顺手。提供了更加细腻的控制流thunk
,dispatch
的参数依然是一个纯粹的 action (FSA)
saga
都是 一个 generator function
,代码可以采用 同步书写 的方式 去处理 异步逻辑(No Callback Hell)
,代码变得更易读generator function
的 saga
实现,代码异常/请求失败 都可以直接通过 try/catch
语法直接捕获处理react
, jquery
,vue
是否有可能共存在一个项目中,答案如下所示:webpack
import
引入的文件,ES6
中的类在es5
中就是构造函数ajax
数据重新获取,答案如下所示:redux
,判断数据有没有,有的话就不要再次请求react-router4
的核心思想是什么,和react-router3
有什么区别,答案如下所示:react-router3
的路由需要在一个文件内容易配置,而react-router4
的理念则是把一个路由当做是一个组件,直接在组件中使用,这是react-router4
和react-router3
在设计理念上的不同immutable.js
和redux
的最佳实践,答案如下所示:immutable
来自于函数式编程的世界,我们可以称它为不可变,相等性检查将包括两个部分,值检查和引用检查React
重新渲染,React
通过对组件属性(props)
和状态(state)
进行变更检查以决定是否更新并重新渲染该组件,若组件状态太过庞大,组件性能就会下降,因为对象越复杂,其相等性检查就会越慢。对于嵌套对象,必须迭代层层进行检查判断,耗费时间过长。若仅修改对象的属性,其引用保持不变,相等性检查中的引用检查结果不变。Immutable
提供一直简单快捷的方式以判断对象是否变更,对于React
组件更新和重新渲染性能可以有较大帮助Immutable
数据,绝对不要突然修改对象,首先复制然后修改复制对象,再返回这个新对象,保持原对象不变。Immutable
数据和原生JavaScript
对象的主要差异为持久化数据结构和结构共享,如下所示:
Trie
构建它不可变的持久性数据结构,它的整体结构可以看作一棵树,一个树节点可以对应代表对象某一个属性,节点值即属性值Immutable Trie
型对象,我们可以把该Trie
型对象想象成如下一棵树,在之后的对象变更尽可能的重用树节点。当我们要更新一个Immutable
对象的属性值时,就是对应着需要重构该Trie
树中的某一个节点,对于Trie
树,我们修改某一节点只需要重构该节点及受其影响的节点,即其祖先节点,如上图中的四个绿色节点,而其他节点可以完全重用React
组件状态必须是一个原生JavaScript
对象,而不能是一个Immutable
对象,因为React
的 setState
方法期望接受一个对象然后使用 Object.assign
方法将其与之前的状态对象合并,如下所示:
Immutable.js
的访问API
访问state
,如 get() , getIn()
Immutable.js
的集合操作生成组件子元素,使用高阶函数如 map() , reduce()
等创React
元素的子元素Immutable.js
的更新操作API
更新state
Immutable.js
与Redux
实践,如下所示:
Immutable
对象中混用原生JavaScript
对象Immutable
对象内添加JavaScript
对象时,首先使用 fromJS()
方法将JavaScript
对象转换为Immutable
对象,然后使用 update() , merge() , set()
等更新API
对Immutable
对象进行更新操作Immutable
对象表示完整的Redux
状态树,对于一个Redux
应用,完整的状态树应该由一个Immutable
对象表示,而没有原生JavaScript
对象fromJS()
方法创建状态树,状态树对象可以是一个Immutable.Record
或者任何其他的实现了 get , set , withMutations
方法的Immutable
集合的实例redux-immutable
库调整 combineReducers
方法使其能处理Immutable
Immutable
方式操作状态对象。为了保证应用性能,在容器组件,选择器(selectors),reducer
函数,action
创建函数,sagas
和thunks
函数内等所有地方均使用Immutable
,但是不在展示型组件内使用Immutable
,容器组件可以使用react-redux
提供的 connect
方法访问redux
的store
,所以我们需要保证选择器(selectors)
总是返回Immutable
对象,否则,将会导致不必要的重新渲染。另外,我们可以使用诸如reselect
的第三方库缓存选择器(selectors)
以提高部分情景下的性能mapStateToProps
方法内使用 toJS() 方法,toJS()
方法每次会调用时都是返回一个原生JavaScript
对象,如果在 mapStateToProps
方法内使用 toJS()
方法,则每次状态树(Immutable对象)
变更时,无论该 toJS()
方法返回的JavaScript
对象是否实际发生改变,组件都会认为该对象发生变更,从而导致不必要的重新渲染toJS()
方法,如果传递给某组件一个Immuatble
对象类型的prop
,则该组件的渲染取决于该Immutable
对象,这将给组件的重用,测试和重构带来更多困难Immutable
类型的属性(props)
传入展示型组件时,需使用高阶组件(HOC)
将其转换为原生JavaScript
对象reselect
是做什么使用的,答案如下所示:vue
里面computed
计算属性的角reselect
的原理是,只要相关状态不变,即直接使用上一次的缓存结果reselect
通过创建选择器(selectors)
,该函数接受一个state
参数,然后返回我们需要在 mapStateToProps
方法内返回对象的某一个数据项,一个选择器的处理可以分为两个步骤,如下所示:
state
参数,根据我们提供的映射函数数组分别进行计算,如果返回结果和上次第一步的计算结果一致,说明命中缓存,则不进行第二步计算,直接返回上次第二步的计算结果,否则继续第二步计算。第一步的结果比较,通常仅仅是 ===
相等性检查,性能是足够的react-router
基本原理,hashHistory,browserHistory
,如下所示:hashHistory
: 不需要后端支持,hashHistory
在#
browserHistory
:还需要后端在服务器上做配置Reloadable
库,路由懒加载,按需加载,访问那个页面加载哪个页面代码XSS
攻击在react
中如何防范,如下所示:react
直接解析字符串,字符串带有script
标签,标签内写有可执行的js
代码,<div dangerouslySetInnerHTML={
{
__html: '' }}></div>
getDerivedStateFromProps,getSnapshotBeforeUpdate
,如下所示:getDerivedStateFromProps和componentWillReceiveProps
差不多,可以对state
进行一些更改getSnapshotBeforeUpdate和componentWillUpdate
差不多,想要获取更新之前的dom
结构可以用它