react底层原理

736 阅读35分钟

二、react

说说React中onClick绑定后的工作原理

  1. 首先react有自己的事件系统,也是遵循w3c的,这个事件系统的名称叫做合成事件(SyntheticEvent),而其自定义事件系统的动机主要包含以下几个方面

    • 抹平不同浏览器之间的兼容性差异。最主要的动机。
    • 件合成既可以处理兼容性问题
    • 提供一个抽象的跨平台事件机制
    • 可以做更多优化
    • 可以干预事件的分发
  2. 当给组件(元素)绑定onClick事件之后

    1. react会对事件先进行注册,将事件统一注册到document
    2. 根据组件唯一的标识key来对事件函数进行存储
    3. 统一的指定dispatchEvent回调函数
    4. 储存事件回调:react会将click这个事件统一存到一个对象中,回调函数的存储采用键值对(key/value)的方式存储在对象中,key 是组件的唯一标识 id,value 对应的就是事件的回调函数,通过组件的key就能回调到相应的函数了

说说react里面bind与箭头函数

  1. bind 由于在类中,采用的是严格模式,所以事件回调的时候会丢失this指向,指向undefined,需要使用bind来给函数绑定上当前实例的this指向
  2. 箭头函数的this指向上下文,所以永久能拿到当前组件实例的this指向,我们可以完美的使用箭头函数来替代传统事件处理函数的回调

说说react中的性能优化

性能优化分为2个方面

  1. 使用shouldComponentUpdate来对state和props进行对比,如果两次的结果一直,那么就return false
  2. 使用纯净组件,pureComponent

高阶组件和高阶函数是什么

  1. 高阶函数:函数接收一个函数作为参数,或者将函数作为返回值的函数就是高阶函数 map some every filter reduce find forEach等等都属于高阶函数
  2. 高阶组价:接受一个组件,返回一个新组建的组件就是高阶组件,本质上和高阶函数的意思一样
  3. 高阶组件是用来复用react代码的一种方式

setState和repalceState的区别

setState 是修改其中的部分状态,相当于 Object. assign,只是覆盖,不会减少原来的状态; replaceState 是完全替换原来的状态,相当于赋值,将原来的 state 替换为另一个对象,如果新状态属性减少,那么 state 中就没有这个状态了

redux中核心组件有哪些,reducer的作用

redux 三大核心

  • action action理解为动作,action的值一般为一个对象,格式如 { type: "", data: "" },type是必须要的,因为reducer处理数据的时候要根据不同的type来进行不同的操作
  • reducer reducer是初始化以及处理派发的action的纯函数
  • store store是一个仓库,用来存储数据,它可以获取数据,也可以派发数据,还能监听到数据的变化

reducer的作用

接收旧的 state 和 action,返回新的 state

什么是受控组件

受控组件就是可以被 react 状态控制的组件 在 react 中,Input textarea 等组件默认是非受控组件(输入框内部的值是用户控制,和React无关)。但是也可以转化成受控组件,就是通过 onChange 事件获取当前输入内容,将当前输入内容作为 value 传入,此时就成为受控组件。

hooks+context和redux你是怎么选择的,都在什么场景下使用

如果项目体量较小,只是需要一个公共的store存储state,而不讲究使用action来管理state,那context完全可以胜任。反之,则是redux的优点。

使用场景: 如果你在组件间传递的数据逻辑比较复杂,可以使用redux; 如果组件层级不多,可以使用props; 如果层级较深,数据逻辑简单,可以使用context。

useffect 模拟生命周期

  • 模拟componentDidMount 第二个参数为一个空数组,可以模拟componentDidMount
componentDidMount:useEffect(()=>{console.log('第一次渲染时调用')},[])
  • 模拟componentDidUpdate 没有第二个参数代表监听所有的属性更新
 useEffect(()=>{console.log('任意状态改变')}) 

监听多个属性的变化需要将属性作为数组传入第二个参数。

 useEffect(()=>{console.log('指定状态改变')},[状态1,状态2...]) 
  • 模拟componentWillUnmount
useEffect(()=>{   ...  return()=>{ //组件卸载前}   })

setsate更新之后和usestate的区别

  • setState( updater [,callback] )

    updater:object/function - 用于更新数据 callback:function - 用于获取更新后最新的 state 值

    构造函数是唯一建议给 this.state 赋值的地方 不建议直接修改 state 的值,因为这样不会重新渲染组件 自动进行浅合并(只会合并第1层) 由于 setState() 异步更新的缘故,依赖 state 旧值更新 state 的时候建议采用传入函数的方式

  • useState(initState)

    const [ state , setState ] = useState(initState) state:状态 setState(updater) :修改状态的方法 updater:object/function - 用于更新数据 initState:状态的初始值

react父组件props变化的时候子组件怎么监听

当props发生变化时执行,初始化render时不执行,在这个回调函数里面,你可以根据属性的变化,通过调用this.setState()来更新你的组件状态,旧的属性还是可以通过this.props来获取,这里调用更新状态是安全的,并不会触发额外的render调用

//props发生变化时触发
componentWillReceiveProps(props) {
	console.log(props)
    this.setState({show: props.checked})
}

usememo在react中怎么使用

返回一个 memoized 值。

把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。

传入 useMemo 的函数会在渲染期间执行。请不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect 的适用范畴,而不是 useMemo。

如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值。

你可以把 useMemo 作为性能优化的手段,但不要把它当成语义上的保证。将来,React 可能会选择“遗忘”以前的一些 memoized 值,并在下次渲染时重新计算它们,比如为离屏组件释放内存。先编写在没有 useMemo 的情况下也可以执行的代码 —— 之后再在你的代码中添加 useMemo,以达到优化性能的目的。

React Hooks各种函数介绍

  • useState:

    useState 是用于声明一个状态变量的,用于为函数组件引入状态. useState 只接收一个参数,这个参数可以是数字、字符串、对象等任意值,用于初始化声明的状态变量。也可以是一个返回初始值的函数,最好是函数,可在渲染时减少不必要的计算。 返回一个长度为2的读写数组,数组的第一项是定义的状态变量本身,第二项是一个用来更新该状态变量的函数,约定是 set 前缀加上状态的变量名. useState Hook 中返回的 setState 并不会帮我们自动合并对象状态的属性; setState 中接收的对象参数如果地址没变的话会被 React 认为没有改变,因此不会引起视图的更新

  • useReducer:

    useReducer 是 useState 的升级版。在 useState 中返回的写接口中,我们只能传递最终的结果,在 setN 的内部也只是简单的赋值操作。

    创建初始状态值initialState,

    创建包含所有操作的 reducer(state, action) 函数,每种操作类型均返回新的 state 值

    根据 initialState 和 reducer 使用 const [state, dispatch] = useReducer(reducer, initialState) 得到读写 API

    调用写接口,传递的参数均挂在 action 对象上

  • useContext:

    context 是上下文的意思,上下文是局部的全局变量这个局部的范围由开发者自己指

  • useEffect:

    effect 是副作用的意思,对环境的改变就是副作用。副作用是函数式编程里的一个概念 在 React 中,useEffect 就是在每次 render 后执行的操作,相当于 afterRender, 接收的第一个参数是回调函数,第二个参数是回调时机。可用在函数组件中模拟生命周期。

    如果同时出现多个 useEffect ,会按出现顺序依次执行

  • useLayoutEffect

    useEffect 总是在浏览器渲染完视图过后才执行,如果 useEffect 里面的回调函数有对 DOM 视图的操作,则会出现一开始是初始化的视图,后来执行了 useEffect 里的回调后立马改变了视图的某一部分,会出现一个闪烁的状态。 为了避免这种闪烁,可以将副作用的回调函数提前到浏览器渲染视图的前面执行,当还没有将 DOM 挂载到页面显示前执行 Effect 中对 DOM 进行操作的回调函数,则在浏览器渲染到页面后不会出现闪烁的状态。

    layout 是视图的意思,useLayoutEffect 就是在视图显示出来前执行的副作用。

    useEffect 和 useLayoutEffect 就是执行的时间点不同,useLayoutEffect 是在浏览器渲染前执行,useEffect 是在浏览器渲染后执行。但二者都是在 render 函数执行过程中运行,useEffect 是在 render 完毕后执行,useLayoutEffect 是在 render 完毕前(视图还没渲染到浏览器页面上)执行。

    因此 useLayoutEffect 总是在 useEffect 前执行。

    一般情况下,如果 Effect 中的回调函数中涉及到 DOM 视图的改变,就应该用 useLayoutEffect,如果没有,则用 useEffect。

  • useRef

    useRef Hook 是用来定义一个在组件不断 render 时保持不变的变量。 组件每次 render 后都会返回一个虚拟 DOM,组件内对应的变量都只属于那个时刻的虚拟 DOM。 useRef Hook 就提供了创建贯穿整个虚拟 DOM 更新历史的属于这个组件的局部的全局变量。 为了确保每次 render 后使用 useRef 获得的变量都能是之前的同一个变量,只能使用引用做到,因此,useRef 就将这个局部的全局变量的值存储到了一个对象中,属性名为:current

    useRef 的 current 变化时不会自动 render

    useRef 可以将创建的 Refs 对象通过 ref 属性的方式引用到 DOM 节点或者 React 实例。

  • useCallback

    是将某个函数“放入到react底层原型链上,并返回该函数的索引”,而useMemo是将某个函数返回值“放入到react底层原型链上,并返回该返回值的索引”。一个是针对函数,一个是针对函数返回值。

  • useImperativeHandle

    useImperativeHandle可以让父组件获取并执行子组件内某些自定义函数(方法)。本质上其实是子组件将自己内部的函数(方法)通过useImperativeHandle添加到父组件中useRef定义的对象中。

  • useMemo

    useMemo可以将某些函数的计算结果(返回值)挂载到react底层原型链上,并返回该函数返回值的索引。当组件重新渲染时,如果useMemo依赖的数据变量未发生变化,那么直接使用原型链上保存的该函数计算结果,跳过本次无意义的重新计算,达到提高组件性能的目的。

React Component和Purecomponent区别

Component 没有直接实现 shouldComponentUpdate 这个方法;但是 PureComponent通过浅层的Porps 和 state 的对比,内部实现了这个生命周期函数。

PureComponent会跳过整个组件子树的props更新,要确保全部的子组件也是 pure 的形式。

Component 中需要手动执行的 shouldComponentUpdate 函数,在PureComponent中已经自动完成了(自动浅对比)。

PureComponent不仅会影响本身,而且会影响子组件,所以PureComponent最好用在数据展示组件中

PureCoponent 如果是复杂数据类型,这里会造成错误的显示(setState浅复制更新,但是界面不会重新渲染)

hooks相对于class的优化

类组件缺点一:复杂且不容易理解的“this” Hooks解决方式:函数组件和普通JS函数非常相似,在普通JS函数中定义的变量、方法都可以不使用“this.”,而直接使用该变量或函数,因此你不再需要去关心“this”了。

类组件缺点二:组件数据状态逻辑不能重用 Hooks解决方式: 通过自定义Hook,可以数据状态逻辑从组件中抽离出去,这样同一个Hook可以被多个组件使用,解决组件数据状态逻辑并不能重用的问题。

类组件缺点三:组件之间传值过程复杂、缺点三:复杂场景下代码难以组织在一起 Hooks解决方式: 通过React内置的useState()函数,可以将不同数据分别从"this.state"中独立拆分出去。降低数据复杂度和可维护性,同时解决类组件缺点三中“内部state数据只能是整体,无法被拆分更细”的问题。

通过React内置的useEffect()函数,将componentDidMount、componentDidUpdate、componentWillUncount 3个生命周期函数通过Hook(钩子)关联成1个处理函数,解决事件订阅分散在多个生命周期函数的问题。

hooks父组件怎么调用子组件的方法

父组件使用 useRef 创建一个 ref 传入 子组件 子组件需要使用 useImperativeHandle 暴露 ref 自定义的实例值给父组件。这个需要用 forwardRef 包裹着。

讲一下react中的通信

1 父子通信, 使用的时候 在子组件上面自定义属性,子组件通过 this.props[累组件]或者props[函数组件]接收然后使用

子组件 
	class Son extends React.Component {
	  
		render(){
				// this.props.til     til	为父组件传递数据
				return <div>...</div>
		}
	}
	父组件
	class Home extends React.Component {
		render(){
			return <>
				<Son  til="参数" />
			</>
		}
	}

2 子父通信 ,使用的时候其实还是父传子,只不过这次将父组件的方法传递给子组件,然后子组件去调用父组件传递的方法,通过传递实参的我方式将子组件的数据传递过来

	class Son extends React.Component {
	  
		render(){
				// this.props.til     til	为父组件传递数据
				
				return <div onClick={()=>this.props.onData('传递的数据')}>...</div>
		}
	}
	父组件
	class Home extends React.Component {
		getData=(data)=>{
			//data就是接收的参数。可以在里面来使用
		}
		render(){
			return <>
				<Son  onGetData={this.getData}/>
			</>
		}
	}

3 状态提升,这种情况将两个组件通过同一个父组件来包裹,将数据绑定到父组件上,然后通过子父和父子进行通信

4 context传递 , 利用Context对象来传递,Context 提供了 Provider 和 Consumer 俩个组件,Provider负责提供数据,Consumer负责使用数据 , Provider需要包裹父组件,那么所有它的子代组件都可以获取到共享值,实际上react-router,react-redux 等等都在使用context

const Context = React.Contexxt('这里是默认值');
// App组件里面
class App extends React.Component {
	render(){
		 return <>
		 		<Context.Provider value={data ‘传递的数据’} >
		 				<Home />  // 只要是home的组件的子代组件都可以获取到传餐
		 		<Context.Provider>
		 </>
	}
}

// Home 组件
class Home extends React.Component {
	  render(){
	  		return <>
	  			<Son />  
	  			<Context.Consumer>
	  				{
	  					data=> data就可以拿到数据
	  				}
	  			</Context.Consumer>
	  		</>
	  }
}

// Son
class Son extends React.Component {
	  static contextType = Context.  // 可以静态接收。接收完之后。可以使用 this.context来调用context的值
	  render(){
	  		return <>
	  		//Son组件里面可以获取到 context的值
	  			<Context.Consumer>
	  				{
	  					data=> data就可以拿到数据
	  				}
	  			</Context.Consumer>
	  		</>
	  }
}

5 reudx. 可以使用redux 来进行组件通信,适用于多个组件使用同一个数据 ,可以更好的维护和使用

react通过什么方法修改参数

类组件修改数据的方法, 通过setState , 注意setState的修改方法有两种,而且它是异步的

函数组件修改方式通过自定义的方法。需要通过 useState , 例如。const [count,setCount] = useState(0)

这里面setCount 可以修改count参数

说你对react native的了解

React native 基于 JavaScript 开发的一个 可以开发原生app的这么一个集成框架,它兼容开发 iOS 和 Android能够实现一套代码,两个系统都能使用,方便维护,相比于web前端的 react ,react-native更好的提供了一些调用手机硬件的API,可以更好的开发移动端,现在react-native它的生态环境也是越来越好,基本上可以完全实现原生开发,

但是现在好多的应用还是用它来套壳 (原生 + web前端),用它来做有些路由,和框架的搭建,然后里面内容来使用前端的react来实现,这样一来让维护更方便,开发更快捷

redux的实现原理

redux它是一个单独的状态管理工具,它是一个数据集中管理的方案,简单来说,就是将公用的数据,放在redux里面进行存储,修改的时候也是利用redux提供的方法来修改,让框架使用的数据的时候更方便,维护起来更容易,reudx提供了一下核心内容:

1 store.

store是redux的核心内容,整个redux的仓库,所有的应用方法都是由store提供

2 createStore

createStore用老创建store的,方法里面有三个参数,有reducer,有中间件,还有初始值,最重要的就是reducer函数,它提供了整个数据管理的逻辑

3 reducer(state, action)

reducer函数相当于数据加工厂,初始的数据,修改数据的 逻辑都统统的在这里完成,它让我们整个的数据走向更完成,维护更方便

4 action

action 本质是一个对象,它来告诉 redux 要执行什么任务

5 state

state它就是我们需要存储的redux的数据,所有的数据都将要在这里面存储

6 store.getState()

它就是用来获取数据的,每次修改前后的数据都可以用store.getState()来修改

7 store.dispatch(action)

用户通过dispatch出发要执行的任务,这个就是出发动作,然后reducer函数会执行,然后进行数据加工

8。store.subscribe(callback)

会自动的监听state,一旦有state发生改变,store.subscribe就会还行,利用它可以用来更新视图

react里的一个输入框每当用户输入文字就会触发onchange,我们怎么拿到他最后输入完的结果

1 第一种是通过受控组件,可以获取到state里面的值,获取修改结果

	class Home extends React.Component {
		state = {
			val:""
		}
		//这是一个通用的写法,然后注意 name的值一定要与state定义的一直
		changeInput = (e) =>{
			let {name,value} = e.target
			this.setState({
				[name]:value
			})
		}
		render(){
			return <>
				<input onChange="this.state.val" />
			</>
		}
	}

2 第二种通过ref来获取里面的值

class Home extends React.Component {
	 render(){
	 	//	  this.val 获取里面的真是dom
	 		return <input ref={node=>this.val} />
	 }
}

react的render什么时候渲染

react生命周期有三个阶段。 两个阶段都会执行 render

主要从更新和挂载两个阶段来讲,挂载阶段都顺序,更新阶段一定要说 shouldComponentUpdate true 和false 分别对应后边是否执行render

	1) 挂在阶段

​			constructor(){}

​			static getDerivedStateFromProps(){

​					return {}

​			}

​		 render(){}.       挂载阶段会执行一次

​		componentDidMount(){}

   	 2 ) 更新阶段

​		static getDerivedStateFromProps(props, state){rerturn {}}

​		shouldComponentUpdate(nextProps, nextState).{ return Boolean. 注意如果 false 则 不向下执行 ,true的时候会执行render}

​		returntruerender() ..		

useEffect的依赖为引用类型如何处理

useEffect的依赖为饮用类型的时候,可能会导致监听不出发,原因就是监听的统一个地址的时候,对象本身地址没变,所以监听的结果就是认为数据并没有改变从而不直径调用

解决方案

1 如果数据是对象的话,可以监听对象的里面的值,值是基本类型,如果值改变了,那么可以监听执行

2 在去修改对象和数据的时候,使用参拷贝或者浅拷贝,这样地址发生改变可以监听执行

3 可以转成字符串,通过JSON.stringify() ,监听字符串这样的,这样改变也会执行

说说在使用Hooks的过程中,需要注意的

	1 首先注意到就是 useState 里面方法是异步的,所以不要在后面连续调用,由于react方法是批量异步调用,并不是每次调用修改方法都执行,所以需要用到callback写法
  const [count, setCount] = useState(0);
  const add = () => {
    setCount(count + 1);
    setCount(count + 1);
    setCount(count + 1);
    setCount(count + 1);  // 就算执行多次 其实还是会只执行一次
    
    //class组件是用一样的
  };
  
  修改后
  const add = () => {
    setCount(count => count + 1);  //这种回调函数的写法
    setCount(count => count + 1);
    setCount(count => count + 1);
    setCount(count => count + 1);
    
    // class组件
    this.setState(prev=>({count: prev.count}))
  };
  

2 由于 useState的是异步的,不要在修改后直接使用数据 , 可以先修改数据,判断数据,也可以利用useEffec,useMemo等等通过监听数据执行

    const [count, setCount] = useState(0);
    
     const add = () => {
    		setCount(count + 1); 
    		if(count>=10) { // 一个逻辑    这样的写的话 会执行上一次吃结果}  
 	  	};
 	  正确使用 1
 	  
 	   const add = () => {
 	    let n = count +1
 	   		if(n){
 	   			//逻辑
 	   		}
    		setCount(n)
 	  	};
 	  	
 	  正确2 :
 	  	 const add = () => [ 	  	   setCount(count+1) 	  	 ]
 	  	 useEffect(()=>{
 	  	 		// 这里是逻辑
 	  	 },[count])
 	  

3 useEffect 这个hook的使用,每一个消耗性能的内容都可以通过return 来消除

	useEffect(()=>{
		// 逻辑1
		return ()=>{
			// 清楚逻辑1的副作用
		}
	},[监听的值])

4 useRef 可以获取组件的数据,也可当常量的值来使用,注意获取数据使用的时候函数组件特别需要注意,如果子组件是函数组件需要利用 useImperativeHandle ,forWard

react组件的key说一下 key发生变化会发生什么 key 值不同销毁前会触发什么生命周期

1 react的key值给组件作为唯一标识,类似身份证,当数组发生增删改查的时候可以通过这个 标识来去对比虚拟dom的更改前后,如果相同标识的数据或者属性没有改变的话,讲直接略过,对比有改变的然后直接改变此项

2 如果给组件添加key,如果key值改变的时候,组件将会重新出创建会执行 挂在阶段的生命周期

constructor

static getDerivedStateFromProps()

render(){}

componentDidUpdate

知道react里面的createPortal么?说说使用场景。之前react没有这个的时候是怎么实现的,有他没他的区别

react.createPortal 这个方法是来使用做弹窗Modal组件的,在没有这个组件之前我们可以自己定义组件,然后去实现Modal效果

Modal.js

const styles = {
  modal: {
    position: 'fixed',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    backgroundColor: 'rgba(0,0,0,0.3)',
    display: 'flex',
    justifyContent: 'center',
    alignItems: 'center'
  }
}
 
class Modal extends Component {
  constructor(props) {
    super(props);
  }
  render() {
    return (
      <div style={styles.modal}>
        {this.props.children}
      </div>
    );
  }
}

react.createPortal 这个来制作弹窗组件,它 在Modal 组件位置进行 fixed 定位,可以任意的挂载到某个dom元素上,使用后的管理更方便,但是注意需要预留html的挂载元素

react全家桶

真正意义上的react全家桶,其实指的是react,react-dom,react-native。 因为react核心库只是vDom的操作,无关dom(dom操作在react-dom中)——这意味着它天生就可以做到跨平台。

注意这里有误区,react-router,react-router-dom,react-dux只是社区的一些使用较多的解决方案,事实上它们各有缺陷。

如何理解fiber?

截止到当前版本(react17.0.2),react尚存在一个明显的缺陷——这是大多数vDom框架都需要面对的——“diff”。 要知道,render前后的两颗vDom tree进行diff,这个过程是不可中断的(以tree为单位,不可中断),这将造成当diff的两颗tree足够庞大的时候,js线程会被diff阻塞,无法对并发事件作出回应。 为了解决这个问题,react将vDom节点上添加了链表节点的特性,将其改造成了fiber节点(其实就是vdom节点结合了链表节点的特性),目的是为了后面的Fiber架构的实现,以实现应对并发事件的“并发模式”。 事实上,原计划是17版本上的,但最终定期在了18版本。

时间分片,任务分炼

fiber节点的改造是为了将diff的单位从不可中断的tree降级到可中断的单一vDom。这意味着每次一个vDom节点比对过后,可以轮训是否发生了优先级更高的并发事件(react将用户想更快得到反馈的事进行了优先级排序,比如js动画等)。 每个vDom的比对是一个时间片单位,因此基于fiber节点的Fiber架构,存在这样的特性: “时间分片,任务分炼,异步渲染。”

hooks

你了解hooks吗?谈谈你对hooks的理解?聊聊hooks?

react-hooks提供给了函数式组件以业务逻辑抽离封装的能力,从单一组件单位进行UI与业务逻辑的分离,以此来保证函数式组件的UI纯净。 在此之前,我们只能通过状态提升的方式,将业务逻辑提升到容器组件的方式。

hooks使用规则?

  • 只可以在顶层调用栈调用hooks

    • 不可以在循环/条件/嵌套里调用hooks
  • 只可以在react function或自定义hooks里调用hooks

    • 原因见hooks调用记录表底层

hooks调用记录表底层_单链表结构

hooks的引用记录,是用单链表结构实现的;(可在力扣/leetcode网找一下实现单链表的算法) 每一个链表节点(node)都有一个next的指针指向下一个node; 如果中间有缺失,那么就无法找到下一个next; 因此hooks不可以在条件里调用(有可能丢失链表node,导致hooks遍历中断);

useEffect与副作用?什么是副作用?聊聊useEffect?

  • 副作用函数 我们将跟UI渲染无关的业务逻辑称之为副作用。 useEffect是react在函数式组件里提供的副作用解决方案,它接受两个参数。 第一个是必传参数,类型为函数。 我们写在此函数中的业务逻辑代码就是我们所说的副作用。
  • 消除副作用函数 默认情况下,useEffct会在每次render后执行传入的副作用函数。 然而有的时候我们需要在执行副作用前先去除掉上次render添加的副作用效果,我们可以在副作用函数里再返回一个函数———这个函数就是消除副作用,它会在每次reRender前和组件卸载时去执行。
  • 监听器 与 useMemo, useCallback等一样,useEffect可以传入一个监听数组参数,这意味着副作用只有在数组中的监听值变化时才会执行。 我们借助它的这个参数特性,可以模拟类组件生命周期钩子————比如componentDidMount等。

生命周期模拟?

useEffect的本质作用,其实是将函数式组件中的业务逻辑副作用进行抽离封装。模拟生命周期并不是它的核心思想,但它当然能做到这个事: github.com/melodyWxy/m…

useLayoutEffect?

与useEffect的区别是,useLayoutEffect中副作用的执行时间在组件render之前,这意味着它适用于比组件render更紧急的副作用。 正因为如此,多数情况下我们不会选择使用它,因为我们并不想阻塞render。

useRef

  • 变量值存储功能 首先,函数式组件每次render就会重新执行一次,这意味着我们在函数组件中定义的变量都会重置,无法保留变更。 useRef可以解决这个问题——它会形成一个闭包作用域来记录这个值。
  • ref的通用功能 当然,我们也可以用它来标记dom和类组件,从而获取对应的实例。

useCallback

同样的,因为函数式组件每次render就会重新执行一次,因此我们在函数式组件中定义的方法都会重新定义————多数情况下这是不必要的,因此我们可以用useCallback来缓存它。 当然,我们也可以基于它第二个参数——一个监听数组,来控制它在何种时机进行重定义。

useMemo

同样的,因为函数式组件每次render就会重新执行一次,因此我们在定义的一些变量或者计算过后的变量,都会重新定义————多数情况下这是不必要的,因此我们可以用useCallback来缓存它。 当然,我们也可以基于它第二个参数——一个监听数组,来控制它在何种时机进行重定义。

redux

Flux

Flux 是一种 架构思想,简单来讲 就是: 状态提升到全局Store + Store内容分发给组件 + 组件交互updateStore然后更新组件。 就是这样一种 通过全局store同一管理数据(从而实现组件间数据共享)的架构思想。

redux与react-redux

基于这种思想,社区里已有各种各样成型的解决方案,其中,redux是通用性比较强的方案(兼容原生和绝大部分框架); 但它的使用较为麻烦,在react项目里,一般用 redux + react-redux + 配置中间件(根据项目需要配置) 这样的方式。 比如 dva = redux + react-redux + redux-saga + router (耦合了router也是dva的诟病所在); 在hooks出现之后,react提供了 useContext这样的钩子,实现了redux的功能。 react-redux 的核心:Provider 与 connect

Provider和connect的底层原理

Provider容器组件底层: 把store打入 contextconnect; 高阶组件底层: 从context拿到store,然后打入到参数组件的props;

  • 什么是高阶组件? 高阶组件:接受一个组件为参数,return 出一个新的组件; 高阶函数:就是一个函数,接受一个函数为参数。

  • redux的核心:

    • createStore 与 disPatchcreateStore: 创建store
    • disPatch: 更新store的方法disPatch的规范/更新store的流程

react-redux流程

  • 状态提升到全局与分发

    • createStore创建全局状态仓库
    • Provider和connnect进行状态的分发
  • 状态更新

    • disPatch 一个 action (action就是一个对象,形如{type:'xxx',...prarams}),基于action.type,触发对应的reducer(reducer是具体更新store的方法,纯函数)逻辑,从而更新store;

react组件的概念(高阶组件,受控组件,非受控组件)

高阶组件:

高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。

一 属性代理
1. 属性代理是最常见的实现方式,它本质上是使用组合的方式,通过将组件包装在容器组件中实现功能。
2. 属性代理方式实现的高阶组件和原组件的生命周期关系完全是React父子组件的生命周期关系,所以该方式实现的高阶组件会影响原组件某些生命周期等方法。

操作 props 抽象 state 获取 refs 引用 获取原组件的 static 方法 通过 props 实现条件渲染

二 反向继承
反向继承指的是使用一个函数接受一个组件作为参数传入,并返回一个继承了该传入组件的类组件,且在返回组件的 render() 方法中返回 super.render() 方法,最简单的实现如下:
受控组件
  1. 受控组件通过props获取其当前值,并通过回调函数(比如onChange)通知变化
  2. 表单状态发生变化时,都会通知React,将状态交给React进行处理,比如可以使用useState存储
  3. 受控组件中,组件渲染出的状态与它的value或checked属性相对应
  4. 受控组件会更新state的流程
非受控组件

非受控组件将数据存储在DOM中,而不是组件内,这比较类似于传统的HTML表单元素。

  1. 非受控组件的值不受组件自身的state和props控制
  2. 非受控组件使用ref从DOM中获取元素数据

react类组件中的生命周期,整体分几步,常用的有哪些

三大阶段,如图 实例期 存在期 销毁期 avatar

render里面为什么不能使用setState

setState会重新执行render函数,如果添加setState会导致死循环

子组件中修改状态时会不会影响父组件,会不会触发生命周期

如果父组件中没有依赖子组件的状态不会 如果父组件中依赖了子组件的状态,因为要执行setState会重新触发生命周期

父组件改变时会不会影响子组件

会,因为会重新执行render函数,如果不想更新子组件可以在shouldComponentUpdate中终止掉

react页面跳转路由传参方法

跳转路由,并向路由组件传递params参数 <Link to={/home/message/tab/${tabItem.id}}>{tabItem.title}

跳转路由,并向路由组件传递search参数 <Link to={/home/message/tab/?id=${tabItem.id}}>{tabItem.title}

跳转路由,并向路由组件传递state参数 <Link to={{pathname:/home/message/tab,state:{id:tabItem.id}}}>{tabItem.title}

使用redux时,setState修改状态的过程是异步还是同步

类组件setState是同步还是异步

  1. 使用合成事件 setState是异步的,想要拿到最新数据需要去回调中
  2. 使用原生事件 setState是同步的

传值过程中,子传值能不能改变父组件,为什么?

不能,props为只读状态 单向数据流

hooks中常用的api有哪些

useState() userContext() userReducer() useEffect()

useeffect中第二个参数有什么作用

如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数。这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行。

react常用的优化手段有哪些

  1. 属性传递优化
  2. 多组件优化
  3. Key
  4. memo
  5. purecomponent
  6. 生命周期
  7. 虚拟列表
  8. 使用纯组件
  9. 懒加载组件
  10. 使用 React Fragments 避免额外标记
  11. 不要使用内联函数定义
  12. 避免 componentWillMount() 中的异步请求
  13. 在 Constructor 的早期绑定函数
  14. 优化 React 中的条件渲染
  15. 不要在 render 方法中导出数据
  16. 为组件创建错误边界
  17. 组件的不可变数据结构

为什么要用redux,怎么修改数据

使用Redux的主要优势之一是它可以帮你处理应用的共享状态。如果两个组件需要访问同一状态,该怎么办?这种两个组件同时需要访问同一状态的现象称为“共享状态”。你可以将该状态提升到附近的父组件,但是如果该父组件在组件树中向上好几个组件的位置,那么将状态当做属性向下一个一个地传递,这项工作很快就会变得乏味。此外,在该父组件和该子组件之间的组件甚至根本不需要访问该状态!

在构建网络应用时,Redux不仅使我们能够以有条理的方式存储数据,而且使我们能够在应用的任何位置快速获取该数据。只需告诉Redux到底哪个组件需要哪个数据,它就会为你处理后续一切工作!

借助Redux,你可以控制状态改变的时间、原因和方式。

useEffect是干什么的

该 Hook 接收一个包含命令式、且可能有副作用代码的函数。 在函数组件主体内(这里指在 React 渲染阶段)改变 DOM、添加订阅、设置定时器、记录日志以及执行其他包含副作用的操作都是不被允许的,因为这可能会产生莫名其妙的 bug 并破坏 UI 的一致性。 使用 useEffect 完成副作用操作。赋值给 useEffect 的函数会在组件渲染到屏幕之后执行。你可以把 effect 看作从 React 的纯函数式世界通往命令式世界的逃生通道。

useEffect能完全模拟componentDidUpdate吗

不可以。 useEffect 并不完全等同于 componentWillUnMount 。当 useEffect 模拟 componentDidMount 和 componentDidUpdate 的时候,返回的函数并不完全等同于 componentWillUnMount。

useLayoutEffect和useEffect的区别

useEffect和useLayoutEffect作为组件的副作用,本质上是一样的。共用一套结构来存储effect链表。整体流程上都是先在render阶段,生成effect,并将它们拼接成链表,存到fiber.updateQueue上,最终带到commit阶段被处理。他们彼此的区别只是最终的执行时机不同,一个异步一个同步,这使得useEffect不会阻塞渲染,而useLayoutEffect会阻塞渲染。

react和vue的数据流,底层原理

React遵循从上到下的数据流向,即单向数据流。
1、单向数据流并非‘单向绑定’,甚至单向数据流与绑定没有‘任何关系’。对于React来说,单向数据流(从上到下)与单一数据源这两个原则,限定了React中要想在一个组件中更新另一个组件的状态
(类似于Vue的平行组件传参,或者是子组件向父组件传递参数),需要进行状态提升。即将状态提升到他们最近的祖先组件中。子组件中Change了状态,触发父组件状态的变更,父组件状态的变更,
影响到了另一个组件的显示(因为传递给另一个组件的状态变化了,这一点与Vue子组件的$emit()方法很相似)。
2、Vue也是单向数据流,只不过能实现双向绑定。
3、单向数据流中的‘单向’-- 数据从父组件到子组件这个流向叫单向。
4、绑定的单双向:View层与Module层之间的映射关系。

react路由和vue路由底层实现原理

eact-router和vue-router实现原理大同小异,都是通过两种方式,hash和history,首先看一下什么是hash和history:

  • hash —— 即地址栏 URL 中的 # 符号(此 hash 不是密码学里的散列运算)。比如这个 URL:www.abc.com/#/hello,hash 的值为 #/hello。它的特点在于:hash 虽然出现在 URL 中,但不会被包括在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面。
  • history —— 利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法。(需要特定浏览器支持)这两个方法应用于浏览器的历史记录栈,在当前已有的 back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。只是当它们执行修改时,虽然改变了当前的 URL,但浏览器不会立即向后端发送请求。
  1. hash模式(vue-router默认hash模式)

hash模式背后的原理是onhashchange事件。

window.onhashchange=function(){
 let hash=location.hash.slice(1);
 document.body.style.color=hash;
}

(localtion是js里管理地址栏的内置对象,是window对象的一部分,可通过window.localtion访问,在w3cshool里的详细介绍)

由于hash发生变化的url都会被浏览器记录下来,使得浏览器的前进后退都可以使用了,尽管浏览器没有亲求服务器,但是页面状态和url关联起来。后来人们称其为前端路由,成为单页应用标配。

比如www.abc.com/#/index,hash值为#/index。hash模式的特点在于hash出现在url中,但是不会被包括在HTTP请求中,对后端没有影响,不会重新加载页面。

2.history模式

当使用history模式时,url就像正常的url,例如abc.com/user/id相比hash模式更加好看。特别注意,history模式需要后台配置支持。如果后台没有正确配置,访问时会返回404。

其中一事件触发用到了 onpopstate。

通过history api,我们丢弃了丑陋的#,但是有一个缺点,当刷新时,如果服务器中没有相应的相应或者资源,会分分钟刷出一个404来(刷新需要请求服务器)。所以history模式不怕前进,不怕后退,就怕刷新。

下述案例为实现的react-router的代码,来了解router大致实现原理

原理: 在dom渲染完成之后,给window 添加 “hashchange” 事件监听页面hash的变化,并且在state属性之中添加了 route属性,代表当前页面的路由。

1、当点击连接 页面hash改变时,触发绑定在window 上的 hashchange 事件,

2、在 hashchange 事件中改变组件的 state中的 route 属性,(react组件的state属性改变时,自动重新渲染页面)

3、页面 随着 state 中的route属性改变自动 根据 不停的hash 给 Child 变量赋值不通的组件,进行渲染

核心代码:

import React from 'react'
import { render } from 'react-dom'
const About = function () {
  return <div>111</div>
}
const Inbox = function () {
  return <div>222</div>
}
const Home = function () {
  return <div>333</div>
}
class App extends React.Component {

 state = {
 	route: window.location.hash.substr(1)
 }

 componentDidMount() {
 	window.addEventListener('hashchange', () => {
 		this.setState({
 			route: window.location.hash.substr(1)
  	})
  })
 }

 render() {
 	let Child
 	switch (this.state.route) {
    case '/about': Child = About; break;
 		case '/inbox': Child = Inbox; break;
 		default: Child = Home;
  }

 return (
   <div>
     <h1>App</h1>
     <ul>
       <li><a href="#/about">About</a></li>
       <li><a href="#/inbox">Inbox</a></li>
     </ul>
     <Child />
   </div>
 )
 }
}

render(<App />, document.body)

axios守卫(请求和响应守卫)

其实这个问题问的是axios请求封装种的请求拦截器和响应拦截器,比如如下的封装代码案例

import axios from 'axios'
import router from './../router'
// development 开发环境   production 生产环境
const isDev = process.env.NODE_ENV === 'development'

// baseURL 会自动加到所有的请求之前
const request = axios.create({
  // http://121.89.205.189/api/pro/list ==> /pro/list
  baseURL: isDev ? 'http://121.89.205.189/api' : 'http://121.89.205.189/api',
  timeout: 6000
})

// 请求拦截器
request.interceptors.request.use(config => {
  // 传入token验证登录状态
  // 如果需要设置其他的信息也可以在这里
  config.headers.common.token = localStorage.getItem('token')
  return config
}, err => Promise.reject(err))
// 响应拦截器封装
request.interceptors.response.use(response => {
  // 如果验证未登录,可以跳转到登录页面
  if (response.data.code === '10119') {
    router.push('/login')
  }
  return response
}, err => Promise.reject(err))

export default request

dva之前有了解吗

这个其实是支付宝整合的一套框架,集成了 (redux + react-router + redux-saga 等)的一层轻量封装。dva 是 framework,不是 library,类似 emberjs。 他最核心的是提供了 app.model 方法,用于把 reducer, initialState, action, saga 封装到一起。

app.model({
  namespace: 'products',
  state: {
    list: [],
    loading: false,
  },
  subscriptions: [
    function(dispatch) {
      dispatch({type: 'products/query'});
    },
  ],
  effects: {
    ['products/query']: function*() {
      yield call(delay(800));
      yield put({
        type: 'products/query/success',
        payload: ['ant-tool', 'roof'],
      });
    },
  },
  reducers: {
    ['products/query'](state) {
      return { ...state, loading: true, };
    },
    ['products/query/success'](state, { payload }) {
      return { ...state, loading: false, list: payload };
    },
  },
});
  • namespace - 对应 reducer 在 combine 到 rootReducer 时的 key 值
  • state - 对应 reducer 的 initialState
  • subscription - elm@0.17 的新概念,在 dom ready 后执行,这里不展开解释
  • effects - 对应 saga,并简化了使用
  • reducers - 相当于数据模型
// 如何创建一个dva项目
$ npm install dva-cli -g
$ dva new myapp && cd myapp
$ npm start

使用了Ant design ,如何自定义样式

方式1:添加global

可以在样式的外部添加:global

 <Form onSubmit={this.handleSubmit} className="account-form">
 </Form>
:global{
	.account-form{
		background-color: red;
	}
}

方式2:定制主题

可以参考链接ant.design/docs/react/…

我们以 webpack@4 为例进行说明,以下是一个 webpack.config.js 的典型例子,对 less-loader 的 options 属性进行相应配置。

// webpack.config.js
module.exports = {
  rules: [{
    test: /.less$/,
    use: [{
      loader: 'style-loader',
    }, {
      loader: 'css-loader', // translates CSS into CommonJS
    }, {
      loader: 'less-loader', // compiles Less to CSS
+     options: {
+       lessOptions: { // 如果使用less-loader@5,请移除 lessOptions 这一级直接配置选项。
+         modifyVars: {
+           'primary-color': '#1DA57A',
+           'link-color': '#1DA57A',
+           'border-radius-base': '2px',
+         },
+         javascriptEnabled: true,
+       },
+     },
    }],
    // ...other rules
  }],
  // ...other config
}

注意:

  1. less-loader 的处理范围不要过滤掉 node_modules 下的 antd 包。
  2. lessOptions 的配置写法在 less-loader@6.0.0 里支持。

React 组件传值的方法

  1. 父组件传递到子组件传值。又称正向传值 采用props进行数据的传递
  2. 子组件传递到父组件传值。又称逆向传值 通过在父组件引入的子组件中传递一个函数并传参,子组件去触发这个函数更改参数完成数据更新
  3. 同级组件传值。又称同胞传值 通过一款插件pubsub-js 这个可以采用发布订阅的方式实现组件间的传值 a组件通过publish进行发布数据。b组件通过subscribe订阅数据
  4. 跨层级传值 通过上下文对象的形式进行数据的传递 通过Provider生产数据 需要接受的组件使用consumer使用数据
  5. redux也可以完成上述传值方式

useEffect模拟生命周期

模拟componentDidMount

hooks 模拟 componentDidMount

useEffect 拥有两个参数,第一个参数作为回调函数会在浏览器布局和绘制完成后调用,因此它不会阻碍浏览器的渲染进程。 第二个参数是一个数组

  • 当数组存在并有值时,如果数组中的任何值发生更改,则每次渲染后都会触发回调。
  • 当它不存在时,每次渲染后都会触发回调。
  • 当它是一个空列表时,回调只会被触发一次,类似于 componentDidMount。
useEffect(()=>{console.log('第一次渲染时调用')},[])

模拟componentDidUpdate

没有第二个参数代表监听所有的属性更新

useEffect(()=>{console.log('任意属性该改变')}) 

监听多个属性的变化需要将属性作为数组传入第二个参数。

useEffect(()=>{console.log('n变了')},[n,m]) 

模拟componentWillUnmount

通常,组件卸载时需要清除 effect 创建的诸如订阅或计时器 ID 等资源 useEffect函数返回的函数可以表示组件卸载了

useEffect(()=>{
	const timer = setTimeout(()=>{
    	...
    },1000)
	return()=>{
    	console.log('组件卸载了')
    	clearTimerout(timer)
    }
})

hooks模拟生命周期

在 React 16.8 之前,函数组件只能是无状态组件,也不能访问 react 生命周期。hook 做为 react 新增特性,可以让我们在不编写 class 的情况下使用 state 以及其他的 react 特性,例如生命周期。接下来我们便举例说明如何使用 hooks 来模拟比较常见的 class 组件生命周期。

  1. hooks 模拟 componentDidMount

    useEffect 拥有两个参数,第一个参数作为回调函数会在浏览器布局和绘制完成后调用,因此它不会阻碍浏览器的渲染进程。 第二个参数是一个数组

    • 当数组存在并有值时,如果数组中的任何值发生更改,则每次渲染后都会触发回调。
    • 当它不存在时,每次渲染后都会触发回调。
    • 当它是一个空列表时,回调只会被触发一次,类似于 componentDidMount。
    useEffect(()=>{console.log('第一次渲染时调用')},[])
    
  2. hooks 模拟 shouldComponentUpdate

    React.memo 包裹一个组件来对它的 props 进行浅比较,但这不是一个 hooks,因为它的写法和 hooks 不同,其实React.memo 等效于 PureComponent,但它只比较 props。

    const MyComponent = React.memo(
        _MyComponent, 
        (prevProps, nextProps) => nextProps.count !== prevProps.count
    )
    
  3. hooks 模拟 componentDidUpdate

    没有第二个参数代表监听所有的属性更新

    useEffect(()=>{console.log('任意属性该改变')}) 
    

    监听多个属性的变化需要将属性作为数组传入第二个参数。

    useEffect(()=>{console.log('n变了')},[n,m]) 
    
  4. hooks 模拟 componentWillUnmount

    通常,组件卸载时需要清除 effect 创建的诸如订阅或计时器 ID 等资源 useEffect函数返回的函数可以表示组件卸载了

    useEffect(()=>{
    	const timer = setTimeout(()=>{
        	...
        },1000)
    	return()=>{
        	console.log('组件卸载了')
        	clearTimerout(timer)
        }
    })
    

react框架怎么做优化

1.render里面尽量减少新建变量和bind函数,传递参数时尽量减少传递参数的数量

第一种是在构造函数中绑定this,会在 构造函数 实例化的时候执行一次

第二种是在render()函数里面绑定this,在每次render()的时候都会重新执行一遍

第三种就是使用箭头函数,每一次render()的时候,都会生成一个新的箭头函数,即使两个箭头函数的内容是一样的。

2.shouldComponentUpdate是决定react组件什么时候能够不重新渲染的函数,但是这个函数默认的实现方式就是简单的返回一个true。也就是说,默认每次更新的时候都要调用所用的生命周期函数,包括render函数,重新渲染。

为了不做不必要的渲染,需要使用shouldComponentUpdate 加以判断

最新的react中,react给我们提供了React.PureComponent

3.使用key进行组件的唯一标识

react的虚拟dom怎么实现

虚拟DOM可以看做一棵模拟了DOM树的JavaScript对象树。在传统的 Web 应用中,我们往往会把数据的变化实时地更新到用户界面中,于是每次数据的微小变动都会引起 DOM 树的重新渲染。虚拟DOM的目的是将所有操作累加起来,统计计算出所有的变化后,统一更新一次DOM。

React会先将你的代码转换成一个javascript对象,然后再把这个javascript对象转换成真实的DOM。而这个javascript对象就是所谓的虚拟DOM。

1.基于babel-preset-react-app把JSX变成React.createElement(...)。可以理解为JSX只是React.createElement的语法糖。

<div className="box" style={{color: '#f00'}} index={0}>
    123
    <p>456</p>
</div>

经过babel转换成下面的样子:

React.createElement("div", {
        className: "box",
        style: {
            color: '#f00'
        },
        index: 0
    }, "123", React.createElement("p", null, "456")
);

2.执行React.createElement(...)会返回一个javascript对象,这个对象就是所谓的虚拟DOM。

createElement会返回一个虚拟对象 let VitrualDomObj = React.createElement(...)

3.把这个虚拟DOM通过render函数转换成真实的DOM

myrender( VitrualDomObj , document.getElementById('root'));

redux和mobx的区别?

1.共同点:

  • 两者都是为了解决状态不好管理,无法有效同步的问题而产生的工具。
  • 都是用来统一管理应用状态的工具
  • 某一个状态只有一个可靠的数据来源
  • 操作更新的方式是统一的,并且是可控的
  • 都支持store与react组件,如react-redux,mobx-react;

2.不同点:

Redux每一次的dispatch都会从根reducer到子reducer嵌套递归的执行,所以效率相对较低;而Mobx的内部使用的是依赖收集,所以不会有这个问题,执行的代码较少,性能相对更高;

Redux核心是不可变对象,在Reducer中的操作都要比较小心,注意不能修改到state的属性,返回时必须是一个全新的对象;而Mobx采用不存在这个问题,操作比较随意;

Redux中写法固定,模板代码较多,Mobx中写法比较随意,但是因为写法随意的原因,如果没有规范性的话,维护性则不会像Redux那么高;

正因为Redux中的reducer更新时,每次return的都是不可变对象,所以时间旅行操作相对容易,而Mobx在这方面不占优势

Redux更加的轻量,但是一般来说都会配合中间件进行使用

react中跨级组件通信中的context是不推荐使用的,为什么?不使用redux还有其它方案吗?

1、Context目前还处于实验阶段,可能会在后面的发行版本中有很大的变化,事实上这种情况已经发生了,所以为了避免给今后升级带来大的影响和麻烦,不建议在app中使用context。 2、尽管不建议在app中使用context,但是独有组件而言,由于影响范围小于app,如果可以做到高内聚,不破坏组件树之间的依赖关系,可以考虑使用context 3、对于组件之间的数据通信或者状态管理,有效使用props或者state解决,然后再考虑使用第三方的成熟库进行解决,以上的方法都不是最佳的方案的时候,在考虑context。 4、context的更新需要通过setState()触发,但是这并不是很可靠的,Context支持跨组件的访问,但是如果中间的子组件通过一些方法不影响更新,比如 shouldComponentUpdate() 返回false 那么不能保证Context的更新一定可以使用Context的子组件,因此,Context的可靠性需要关注。

其他的解决方案可以使用

  1. mobx
  2. useContext + useReducer

react-router底层实现原理

顶层Router订阅history,history变化时,Router调用setState将location向下传递,并设置到RouterContext。Route组件匹配context中的location决定是否显示。Switch选择最先匹配到的显示,利用props children。Link组件阻止a标签默认事件,并调用history.push。NavLink通过匹配context中的location决定是否为active状态。Redirect组件匹配context里的location决定是否调用history.push(to),Switch组件会匹配location和from决定是否发起Redirect。

react为什么是单向数据流,react有什么特性

单向数据流就是:数据在某个节点被改动后,只会影响一个方向上的其他节点。数据只会影响到下一个层级的节点,不会影响上一个层级的节点。

在react中Props 为只读的,而不是可更改的。这也就是数据更新不能直接通过 this.state 操作,想要更新,就需要通过 React 提供的专门的 this.setState() 方法来做。

单向数据流其实就是一种框架本身对数据流向的限制。保证数据的可控性。

react使用的是jsx,模板的好处是什么?

JSX 是 JavaScript 的一种扩展,为函数调用和对象构造提供了语法糖,特别是 React.createElement()。JSX 看起来可能更像是模板引擎或 HTML,但它不是。JSX 生成 React 元素,同时允许你充分利用 JavaScript 的全部功能。

JSX 是编写 React 组件的极好方法,有以下优点:

改进的开发人员体验:代码更易读,因为它们更加形象,感谢类 XML 语法,从而可以更好地表示嵌套的声明式结构。 更具生产力的团队成员:非正式开发人员可以更容易地修改代码,因为 JSX 看起来像HTML,这正是他们所熟悉的。 更少的语法错误:开发人员需要敲入的代码量变得更少,这意味着他们犯错的概率降低了,能够减少重复性伤害。

antd常用的组件

  • 常用组件有:Table、Form、Grid、Button、Menu、Upload、DatePicker、Pagination等。

antd弹出层的原理

  • 弹出层组件使用了 ReactDOM.createPortal(child, container) 来实现的。
  • React Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案。

类组件和函数组件的区别

  1. 函数组件看似只是一个返回值是DOM结构的函数,其实它的背后是无状态组件(Stateless Components)的思想。函数组件中,你无法使用State,也无法使用组件的生命周期方法,这就决定了函数组件都是展示性组件(Presentational Components),接收Props,渲染DOM,而不关注其他逻辑。
  2. 函数组件中没有this。所以你再也不需要考虑this带来的烦恼。而在类组件中,你依然要记得绑定this这个琐碎的事情。如示例中的sayHi。
  3. 函数组件更容易理解。当你看到一个函数组件时,你就知道它的功能只是接收属性,渲染页面,它不执行与UI无关的逻辑处理,它只是一个纯函数。而不用在意它返回的DOM结构有多复杂。
  4. 性能。目前React还是会把函数组件在内部转换成类组件,所以使用函数组件和使用类组件在性能上并无大的差异。但是,React官方已承诺,未来将会优化函数组件的性能,因为函数组件不需要考虑组件状态和组件生命周期方法中的各种比较校验,所以有很大的性能提升空间。
  5. 函数组件迫使你思考最佳实践。这是最重要的一点。组件的主要职责是UI渲染,理想情况下,所有的组件都是展示性组件,每个页面都是由这些展示性组件组合而成。如果一个组件是函数组件,那么它当然满足这个要求。所以牢记函数组件的概念,可以让你在写组件时,先思考这个组件应不应该是展示性组件。更多的展示性组件意味着更多的组件有更简洁的结构,更多的组件能被更好的复用。

在React组件复用与组合 中我们会提到,应当避免在底层的展示性组件中混入对于状态的管理,而应该将状态托管于某个高阶组件或者其他的状态容器中。利用函数式声明组件可以彻底保证不会在组件中进行状态操作。

双向数据绑定和单向数据流的优缺点

  1. 单向数据流 数据流动方向可以跟踪,流动单一,追查问题的时候可以跟快捷。缺点就是写起来不太方便。要使UI发生变更就必须创建各种action来维护对应的state
  2. 单向数据流其实是没有状态的, 这使得单向绑定能够避免状态管理在复杂度上升时产生的各种问题, 程序的调试会变得相对容易, 但强制要求编码时设计思维的一致性。
  3. 双向流动值和UI双绑定,这种好处大家都懂。但是由于各种数据相互依赖相互绑定,导致数据问题的源头难以被跟踪到,子组件修改父组件,兄弟组件互相修改有有违设计原则。 但好处就是 太方便使用了。
  4. 双向数据流是自动管理状态的, 但是在实际应用中会有很多不得不手动处理状态变化的逻辑, 使得程序复杂度上升, 难以调试, 但程序各个部分自成一体, 不强制要求一致的编码。 如果你的程序需要一个统一的数据源, 应该选择单向数据流, 所有的数据变化都是可观测的, 数据自上而下流动, 即使出问题也很容易找到源头.
  5. 如果你的程序本身有多个数据源, 或者是程序的逻辑本身会产生很多的副作用, 应该选择双向绑定的程序, 将大项目分化为小项目, 逐个击破。
  6. 单向绑定使得数据流也是单向的,对于复杂应用来说这是实施统一的状态管理(如redux)的前提。 双向绑定在一些需要实时反应用户输入的场合会非常方便(比如多级联动菜单)。但通常认为复杂应用中这种便利比不上引入状态管理带来的优势。

setState是同步还是异步

  1. 在 React 的 setState 函数实现中,会根据一个变量 isBatchingUpdates 判断是直接更新 this.state 还是放到一个updateQueue中延时更新,而 isBatchingUpdates 默认是 false,表示 setState 会同步更新 this.state;但是,有一个函数 batchedUpdates,该函数会把 isBatchingUpdates 修改为 true,而当 React 在调用事件处理函数之前就会先调用这个 batchedUpdates将isBatchingUpdates修改为true,这样由 React 控制的事件处理过程 setState 不会同步更新 this.state,而是异步的。
  2. 所以说setstate本身是同步的,一旦走了react内部的合并逻辑,放入了updateQueue队列中就变成异步了,而代码中的函数是react控制的,内部会走合并逻辑,所以这里的setState 不但是合并的也是异步的
  3. 上面说了,setState是异步的原因是因为走了react内部的合并逻辑,那只要能绕过react内部的合并逻辑,不让它进入到updateQueue中不就变成同步了吗?因为setState()本身就是同步的
  4. 利用setTimeout绕过react内部的合并逻辑
  5. 异步的情况: 由React控制的事件处理函数,以及生命周期函数调用setState时表现为异步 。大部分开发中用到的都是React封装的事件,比如onChange、onClick、onTouchMove等(合成事件中),这些事件处理函数中的setState都是异步处理的。

同步的情况: React控制之外的事件中调用setState是同步更新的。比如原生js绑定的事件,setTimeout/setInterval,ajax,promise.then内等 React 无法掌控的 APIs情况下,setState是同步更新state的

redux和context+hooks优缺点

  • 数据流对比

redux

hooks

  • 简单分析 redux 的数据流程图画得比较简单,理解大概意思就好,毕竟它不是我要说的重点,和 hooks 的数据流程相比其实是大同小异。

    从 hooks 数据流能大致看出来,我们设计好 store 后,通过对应的 hooks 函数生成每个 store 的 Provider 和 Context。我们把所有的单个 Provider 糅合为一个整体的 Providers,作为所有 UI 的父组件。

    在任何一个子 UI 组件内部,通过 hooks 函数得到对应 store 的 state、dispatch。UI 组件内,通过主动调用 dispatch 发送 action,然后经过 store 的数据处理中心 reducer,就能触发相应的数据改变。这个数据流程和 redux 几乎一模一样。

  • 相同点 统一 store 数据管理 支持以发送 action 来修改数据 支持 action 处理中心:reducer

  • 异同点 hooks UI 层获取 store 和 dispatch 不需要用 HOC 依赖注入,而是用 useContext redux 在 action 之后改变视图本质上还是 state 注入的方式修改的组件内部 state,而 hooks 则是一对一的数据触发 hooks 的 reducer 来自于 useReducer hooks 还没有 middleware 的解决方案

react的生命周期

  • react旧版生命周期包含三个过程

1、挂载过程 constructor() componentWillMount() componentDidMount()

2、更新过程 componentWillReceiveProps(nextProps) shouldComponentUpdate(nextProps,nextState) componentWillUpdate (nextProps,nextState) render() componentDidUpdate(prevProps,prevState)

3、卸载过程 componentWillUnmount()

其具体作用分别为: 1、constructor() 完成了React数据的初始化。

2、componentWillMount() 组件已经完成初始化数据,但是还未渲染DOM时执行的逻辑,主要用于服务端渲染。

3、componentDidMount() 组件第一次渲染完成时执行的逻辑,此时DOM节点已经生成了。

4、componentWillReceiveProps(nextProps) 接收父组件新的props时,重新渲染组件执行的逻辑。

5、shouldComponentUpdate(nextProps, nextState) 在setState以后,state发生变化,组件会进入重新渲染的流程时执行的逻辑。在这个生命周期中return false可以阻止组件的更新,主要用于性能优化。

6、componentWillUpdate(nextProps, nextState) shouldComponentUpdate返回true以后,组件进入重新渲染的流程时执行的逻辑。

7、render() 页面渲染执行的逻辑,render函数把jsx编译为函数并生成虚拟dom,然后通过其diff算法比较更新前后的新旧DOM树,并渲染更改后的节点。

8、componentDidUpdate(prevProps, prevState) 重新渲染后执行的逻辑。

9、componentWillUnmount() 组件的卸载前执行的逻辑,比如进行“清除组件中所有的setTimeout、setInterval等计时器”或“移除所有组件中的监听器removeEventListener”等操作。

  • react新版生命周期

react16.4后使用了新的生命周期,使用getDerivedStateFromProps代替了旧的componentWillReceiveProps及componentWillMount。使用getSnapshotBeforeUpdate代替了旧的componentWillUpdate。

使用getDerivedStateFromProps(nextProps, prevState)的原因: 旧的React中componentWillReceiveProps方法是用来判断前后两个 props 是否相同,如果不同,则将新的 props 更新到相应的 state 上去。在这个过程中我们实际上是可以访问到当前props的,这样我们可能会对this.props做一些奇奇怪怪的操作,很可能会破坏 state 数据的单一数据源,导致组件状态变得不可预测。

而在 getDerivedStateFromProps 中禁止了组件去访问 this.props,强制让开发者去比较 nextProps 与 prevState 中的值,以确保当开发者用到 getDerivedStateFromProps 这个生命周期函数时,就是在根据当前的 props 来更新组件的 state,而不是去访问this.props并做其他一些让组件自身状态变得更加不可预测的事情。

使用getSnapshotBeforeUpdate(prevProps, prevState)的原因: 在 React 开启异步渲染模式后,在执行函数时读到的 DOM 元素状态并不总是渲染时相同,这就导致在 componentDidUpdate 中使用 componentWillUpdate 中读取到的 DOM 元素状态是不安全的,因为这时的值很有可能已经失效了。

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

React-redux中有个Context使用过吗?用来干什么的

这里再次提及下Redux的Store上的3个api,

  store.getState()  // 获得 store 上存储的所有状态
  store.dispatch() // View 发出 Action 的唯一方法
  store.subscribe() // 订阅 store上State 发生变化

虽然 Redux 应用全局就一个 Store 这样的直接导入依然有问题,因为在组件中直接导人 Store 是非常不利于组件复用的 。

所以,一个应用中,最好只有一个地方需要直接导人 Store ,这个位置当然应该是在调用最顶层 React 组件的位置 。 但是这样需要把Store,从顶层一层层往下传递,首先我们想到的就是props(父子组件通信方案)。这种方法有一个很大的缺陷,就是从上到下,所有的组件都要帮助传递这个 props 。

设想在一个嵌套多层的组件结构中,只有最里层的组件才需要使用 store ,但是为了把 store 从最外层传递到最里层,就要求中间所有的组件都需要增加对这个 store prop 的支持,即使根本不使用它,这无疑增加程序的耦合度,复杂度和不可维护性 。

React 提供了一个 叫 Context 的功能,能够完美地解决这个问题 。

redux-thunk是干什么的,为什么要用这个?具体怎么实现

  1. 什么是thunk?

    thunk可以理解为函数的另一种称呼,但不是以前我们说的简单函数。Thunk代表的是一个被其他函数返回的函数。

  2. 那么它是怎么作用在redux上的呢? Redux中有几个概念:”actions”,”action creators”,”reducer”,“middleware”

Actions就是普通对象,并且一定有一个type属性,除了这个,你可以在对象中自定义你想要描述该action的数据。

  // 1. plain object
  // 2. has a type
  // 3. whatever else you want
  {
    type: "USER_LOGGED_IN",
    username: "dave"
  }

每次都要手写action对象会很麻烦,redux有一个“action creators”的概念来简化。

function userLoggedIn() {
  return {
    type: 'USER_LOGGED_IN',
    username: 'dave'
  };
}

你可以通过调用userLoggedIn函数来生成这个action,如果你在app中多次dispatch同样的action,使用action creators会大大简化工作。

  1. Action可以做点什么吗? Action虽然叫action,但其实什么都不做,只是对象而已。它们可以自己做点什么吗?比如像调用api或是触发其他actions? 因为reducer必须是纯函数,我们不能在reducer里调用api或是触发其他actions。

如果我们希望action做些什么,就要把代码放进一个函数中去,这个函数就是thunk。

Action creators返回一个函数而不是一个action对象。

function getUser() {
  return function() {
    return axios.get('/current_user');
  };
}
  1. 那么怎么让Redux处理actions是函数的情况呢? 我们就引入了redux-thunk,它是一个中间件,每次action都会经历它,如果action是一个函数,就调用这个函数,这就是redux-thunk做的事。

传递给thunk函数的就是两个参数:

  • Dispatch,如果它们需要,可以dispatch新的action。

  • getState,它们可以获取最新的state。

    function logOutUser() {
        return function(dispatch, getState) {
          return axios.post('/logout').then(function() {
            // pretend we declared an action creator
            // called 'userLoggedOut', and now we can dispatch it
            dispatch(userLoggedOut());
          });
        };
      }
    
  1. 如何使用redux-thunk
  • 安装redux-thunk包 npm install --save redux-thunk
  • 导入redux-thunk并插入redux
  import { createStore, applyMiddleware } from 'redux';
  import thunk from 'redux-thunk';
  import rootReducer from './reducers/index';

  const store = createStore(
    rootReducer,
    applyMiddleware(thunk)
  );

怎么用 React-redux 解决数据发生改变就会刷新页面这个问题

通常情况下,store 状态数据发生改变,会刷新页面这个问题是由于设计导致的,可能是组件定义时的问题。建议不要将页面中使用的组件都包装到一个大的容器组件中(如图1所示),而是将各相关的组件包装到一个较小的容器组件中(如图2所示):

图1 图2

图1中,当容器组件中订阅的 store 发生变化时,所有组件都会重新渲染。在图2中,由于将相关组件包装到了较小的容器组件中,当 container2 容器中订阅的 store 发生变化时,仅相关的组件重新渲染,其它组件不会导致重新渲染。

一般什么时候使用redux

在遇到如下情况时,可以考虑使用 redux:

  • 某个状态需要在全局使用或共享(例如角色权限等信息)
  • state并不总是以单向的方式线性流动
  • UI 可以根据应用程序状态显著变化
  • 许多不相关的组件以相同的方式更新状态
  • 状态树结构复杂

redux有什么优缺点

优点:

  • 纯粹,清晰易懂,让状态可预测,方便测试

缺点:

  • 繁琐,要写大量的 action creator、reducer,所以即便想进行一个小的状态变化也会需要更改好几个地方
  • 需要写大量 switch case 分支判断,造成不小的负担
  • 状态是存在于顶层的一棵树,通过 connect 和组件连在一起,单个组件是无法独立工作的。
  • 对于各种异步的副作用,redux 本身是无能为力的,需要使用中间件

类组件有状态,为什么还要用 redux

类组件有 state,但 React 推荐组件间传递数据使用 props 自上而下流动,如果涉及到跨组件层级的通信,涉及到全局组件状态数据的共享及更新等,在各组件中都添加 props 来传递数据会显得非常繁琐,利用 redux 可以简化状态更新的繁琐度,提供统一的状态更新方式,使得状态更新可预测,也方便调试与维护。

shouldComponentUpdate 的作用

此方法仅作为性能优化的方式而存在,根据 shouldComponentUpdate() 的返回值,判断 React 组件的输出是否受当前 state 或 props 更改的影响,返回 false 以告知 React 可以跳过更新。

示例:

这是一个组件的子树。每个节点中,SCU 代表 shouldComponentUpdate 返回的值,而 vDOMEq 代表返回的 React 元素是否相同。最后,圆圈的颜色代表了该组件是否需要被调停。

节点 C2 的 shouldComponentUpdate 返回了 false,React 因而不会去渲染 C2,也因此 C4 和 C5 的 shouldComponentUpdate 不会被调用到。

对于 C1 和 C3,shouldComponentUpdate 返回了 true,所以 React 需要继续向下查询子节点。这里 C6 的 shouldComponentUpdate 返回了 true,同时由于渲染的元素与之前的不同使得 React 更新了该 DOM。

最后一个有趣的例子是 C8。React 需要渲染这个组件,但是由于其返回的 React 元素和之前渲染的相同,所以不需要更新 DOM。

显而易见,你看到 React 只改变了 C6 的 DOM。对于 C8,通过对比了渲染的 React 元素跳过了渲染。而对于 C2 的子节点和 C7,由于 shouldComponentUpdate 使得 render 并没有被调用。因此它们也不需要对比元素了。

react 中 ref 的作用

ref 是 React 提供的用来操纵 React 组件实例或者操作 DOM 元素的技术。

适合使用 ref 的几种情况:

  • 管理焦点,文本选择或媒体播放
  • 触发强制动画
  • 集成第三方 DOM 库

示例1:

class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    // 创建一个 ref 来存储 textInput 的 DOM 元素
    this.textInput = React.createRef();
    this.focusTextInput = this.focusTextInput.bind(this);
  }

  focusTextInput() {
    // 直接使用原生 API 使 text 输入框获得焦点
    // 注意:我们通过 "current" 来访问 DOM 节点
    this.textInput.current.focus();
  }

  render() {
    // 告诉 React 我们想把 <input> ref 关联到
    // 构造器里创建的 `textInput` 上
    return (
      <div>
        <input
          type="text"
          ref={this.textInput} />
        <input
          type="button"
          value="Focus the text input"
          onClick={this.focusTextInput}
        />
      </div>
    );
  }
}

这个例子是为 DOM 元素添加 ref,使用 ref 去存储 DOM 节点的引用,React 会在组件挂载时给 ref 对象的 current 属性传入 DOM 元素,并在组件卸载时传入 null 值。ref 会在 componentDidMountcomponentDidUpdate 生命周期钩子触发前更新。

示例2:

class AutoFocusTextInput extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = React.createRef();
  }

  componentDidMount() {
    this.textInput.current.focusTextInput();
  }

  render() {
    return (
      <CustomTextInput ref={this.textInput} />
    );
  }
}

这个示例是包装上面的 CustomTextInput,来模拟它挂载之后立即被点击的操作,我们可以使用 ref 来获取这个自定义的 input 组件并手动调用它的 focusTextInput 方法。

解释 redux 的工作流程

Redux工作流程:

  1. 在 View 中派发 action(dispatch(action))
  2. store 接收到派发的 action 对象
  3. 在 store 中,调用 reducer(),并传递之前的 state 与 action 作为参数
  4. 在 reducer 中按照 action.type 判断,运算并返回新的 state 给 store
  5. store 中更新最新的 state
  6. 执行由 subscribe() 注册的监听任务(这些任务订阅了 store 的变化)

redux 数据流向,比如点击按钮到状态修改,数据如何流向的

由上图我们可以看到,redux 数据流向大致步骤(以按钮点击修改状态为例):

  1. UI 首次呈现时,访问 redux 存储的当前状态 state,并使用该数据来决定初始呈现内容:显示提现 $0 元。
  2. 在视图中触发点击事件:提现
  3. 事件处理程序中,触发 action 并传递给 store:dispatch(action)
  4. store 使用前一个 state 和接收到的 action 经 reducer 函数处理,并将返回值保存为新的 state
  5. UI 根据新状态重新呈现

说一下 redux 的使用流程,dispash 是从哪里解构出来的

redux 使用流程:

  1. 创建 reducer() 纯函数,用于同步更新状态
  2. 将各独立的 reducer 合并为一个完整的根 reducer
  3. 创建 action creator 函数,用于生成 action 对象
  4. 基于根 reducer 创建 store,如果有异步更新状态数据操作,应用中间件(如 redux-thunk)
  5. 订阅 store 中的状态变化
  6. 在 UI 中根据 store 的状态初始渲染
  7. 在 UI 中触发 action 到 store 中更新状态

如果我们要在 View 中实现 store 中的状态更新,则需要 dispatch(action),这个 dispatch() 方法是从创建的 store 中解构出来的,在 store 中除了 dispatch 外,还可以解构诸如 getState、subscribe 等属性。

React性能为什么强于vue

对于框架之间我们并不是特别在意谁强谁弱, 而是要搞清楚框架真正值得我们学习的点:

像React框架, 它在 架构上融合了数据驱动视图、组件化、函数式编程、面向对象、Fiber 等经典设计“哲学”, 在底层技术选型上涉及了 JSX、虚拟 DOM等经典解决方案,在周边生态上至少涵盖了状态管理和前端路由两 大领域的最佳实践。此外,它还自建了状态管理机制与事件系统,创造性地在前端框架中引入了 Hooks 思 想...... React 十年如一日的稳定输出背后,有太多值得我们去吸收和借鉴的东西.

  1. npm源码包大小, react 291kb, vue 2.97mb 更小的源码包, 更少的cpu消耗.
  2. 基于虚拟DOM, 减少重绘次数(将多次数据操作汇集成一次DOM更新); 减少手动操作DOM操作(不用再像以前写jQuery那样,先获取DOM元素,再设置属性)
  3. Fiber算法, React16提出了Fiber架构,其能够将任务分片,划分优先级,同时能够实现类似于操作系统中对线程的抢占式调度,非常强大, 对于因为JavaScript的单线程特性,单个同步任务耗时太长,出现卡顿的问题就可以得到解决, 这进一步弥补了React在组件更新机制方面的缺陷.
  4. Hooks, 创造性地在前端框架中引入了 Hooks , 这使得程序员有了除class component以外的界面构建方式, Hooks让复用代码这件事变得更容易, 结合函数式组件整体风格更清爽,更优雅, 更少的代码量, 这也使得项目更容易阅读和维护.
  5. 结合shouldComponentUpdate等方法, 可以避免不必要的组件更新, 实现更少的cpu消耗, 程序员可以从代码的角度介入到组件更新效率的控制过程中.

vue-router和react-router的区别

  • 路由配置语法的差异

    • vue-router是全局配置方式,react-router是全局组件方式。
    • vue-router仅支持对象形式的配置,react-router支持对象形式和JSX语法的组件形式配置。
    • vue-router任何路由组件都会被渲染到位置,react-router子组件作为children被传入父组件,而根组件被渲染到位置。
  • 使用方式上的不同

    • vue-router通过路由配置表 + router-link, router-view控制路由.
    • react-router通过Link,NavLink, Route,Switch,Redirect等路由组件控制路由,
  • 获取参数的方式不同

    • vue-router借助this.router获取全局路由的实例,this.router获取全局路由的实例,this.route获取当前路由信息
    • react-router借助this.props.location, this.props.match获取路由参数