state 和 props 触发更新的生命周期别离有什么区别?
state 更新流程: 这个过程当中波及的函数:
- shouldComponentUpdate: 当组件的 state 或 props 产生扭转时,都会首先触发这个生命周期函数。它会接管两个参数:nextProps, nextState——它们别离代表传入的新 props 和新的 state 值。拿到这两个值之后,咱们就能够通过一些比照逻辑来决定是否有 re-render(重渲染)的必要了。如果该函数的返回值为 false,则生命周期终止,反之持续;
留神:此办法仅作为性能优化的形式而存在。不要希图依附此办法来“阻止”渲染,因为这可能会产生 bug。应该思考应用内置的 PureComponent 组件,而不是手动编写 shouldComponentUpdate()
- componentWillUpdate:当组件的 state 或 props 产生扭转时,会在渲染之前调用 componentWillUpdate。componentWillUpdate 是 React16 废除的三个生命周期之一。过来,咱们可能心愿能在这个阶段去收集一些必要的信息(比方更新前的 DOM 信息等等),当初咱们齐全能够在 React16 的 getSnapshotBeforeUpdate 中去做这些事;
- componentDidUpdate:componentDidUpdate() 会在UI更新后会被立刻调用。它接管 prevProps(上一次的 props 值)作为入参,也就是说在此处咱们依然能够进行 props 值比照(再次阐明 componentWillUpdate 的确鸡肋哈)。
props 更新流程: 绝对于 state 更新,props 更新后惟一的区别是减少了对 componentWillReceiveProps 的调用。对于 componentWillReceiveProps,须要晓得这些事件:
- componentWillReceiveProps:它在Component承受到新的 props 时被触发。componentWillReceiveProps 会接管一个名为 nextProps 的参数(对应新的 props 值)。该生命周期是 React16 废除掉的三个生命周期之一。在它被废除前,能够用它来比拟 this.props 和 nextProps 来从新setState。在 React16 中,用一个相似的新生命周期 getDerivedStateFromProps 来代替它。
React组件的state和props有什么区别?
(1)props
props是一个从内部传进组件的参数,次要作为就是从父组件向子组件传递数据,它具备可读性和不变性,只能通过内部组件被动传入新的props来从新渲染子组件,否则子组件的props以及展示模式不会扭转。
(2)state
state的次要作用是用于组件保留、管制以及批改本人的状态,它只能在constructor中初始化,它算是组件的公有属性,不可通过内部拜访和批改,只能通过组件外部的this.setState来批改,批改state属性会导致组件的从新渲染。
(3)区别
- props 是传递给组件的(相似于函数的形参),而state 是在组件内被组件本人治理的(相似于在一个函数内申明的变量)。
- props 是不可批改的,所有 React 组件都必须像纯函数一样爱护它们的 props 不被更改。
- state 是在组件中创立的,个别在 constructor中初始化 state。state 是多变的、能够批改,每次setState都异步更新的。
React中什么是受控组件和非控组件?
(1)受控组件 在应用表单来收集用户输出时,例如<input><select><textearea>
等元素都要绑定一个change事件,当表单的状态发生变化,就会触发onChange事件,更新组件的state。这种组件在React中被称为受控组件,在受控组件中,组件渲染出的状态与它的value或checked属性绝对应,react通过这种形式打消了组件的部分状态,使整个状态可控。react官网举荐应用受控表单组件。
受控组件更新state的流程:
- 能够通过初始state中设置表单的默认值
- 每当表单的值发生变化时,调用onChange事件处理器
- 事件处理器通过事件对象e拿到扭转后的状态,并更新组件的state
- 一旦通过setState办法更新state,就会触发视图的从新渲染,实现表单组件的更新
受控组件缺点: 表单元素的值都是由React组件进行治理,当有多个输入框,或者多个这种组件时,如果想同时获取到全副的值就必须每个都要编写事件处理函数,这会让代码看着很臃肿,所以为了解决这种状况,呈现了非受控组件。
(2)非受控组件 如果一个表单组件没有value props(单选和复选按钮对应的是checked props)时,就能够称为非受控组件。在非受控组件中,能够应用一个ref来从DOM取得表单值。而不是为每个状态更新编写一个事件处理程序。
React官网的解释:
要编写一个非受控组件,而不是为每个状态更新都编写数据处理函数,你能够应用 ref来从 DOM 节点中获取表单数据。
因为非受控组件将实在数据贮存在 DOM 节点中,所以在应用非受控组件时,有时候反而更容易同时集成 React 和非 React 代码。如果你不介意代码好看性,并且心愿疾速编写代码,应用非受控组件往往能够缩小你的代码量。否则,你应该应用受控组件。
例如,上面的代码在非受控组件中接管单个属性:
class NameForm extends React.Component { constructor(props) { super(props); this.handleSubmit = this.handleSubmit.bind(this); } handleSubmit(event) { alert('A name was submitted: ' + this.input.value); event.preventDefault(); } render() { return ( <form onSubmit={this.handleSubmit}> <label> Name: <input type="text" ref={(input) => this.input = input} /> </label> <input type="submit" value="Submit" /> </form> ); }}
总结: 页面中所有输出类的DOM如果是现用现取的称为非受控组件,而通过setState将输出的值保护到了state中,须要时再从state中取出,这里的数据就受到了state的管制,称为受控组件。
如何防止反复发动ajax获取数据?
- 数据放在redux外面
在应用 React Router时,如何获取以后页面的路由或浏览器中地址栏中的地址?
在以后组件的 props中,蕴含 location属性对象,蕴含以后页面路由地址信息,在 match中存储以后路由的参数等数据信息。能够间接通过 this .props应用它们。
connect原理
- 首先
connect
之所以会胜利,是因为Provider
组件: - 在原利用组件上包裹一层,使原来整个利用成为
Provider
的子组件 接管Redux
的store
作为props
,通过context
对象传递给子孙组件上的connect
connect
做了些什么。它真正连贯Redux
和React
,它包在咱们的容器组件的外一层,它接管下面Provider
提供的store
外面的state
和dispatch
,传给一个构造函数,返回一个对象,以属性模式传给咱们的容器组件
connect
是一个高阶函数,首先传入mapStateToProps
、mapDispatchToProps
,而后返回一个生产Component
的函数(wrapWithConnect
),而后再将真正的Component
作为参数传入wrapWithConnect
,这样就生产出一个通过包裹的Connect
组件,
该组件具备如下特点
- 通过
props.store
获取先人Component
的store props
包含stateProps
、dispatchProps
、parentProps
,合并在一起失去nextState
,作为props
传给真正的Component componentDidMount
时,增加事件this.store.subscribe(this.handleChange)
,实现页面交互 shouldComponentUpdate
时判断是否有防止进行渲染,晋升页面性能,并失去nextState
componentWillUnmount
时移除注册的事件this.handleChange
因为connect
的源码过长,咱们只看次要逻辑
export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) { return function wrapWithConnect(WrappedComponent) { class Connect extends Component { constructor(props, context) { // 从先人Component处取得store this.store = props.store || context.store this.stateProps = computeStateProps(this.store, props) this.dispatchProps = computeDispatchProps(this.store, props) this.state = { storeState: null } // 对stateProps、dispatchProps、parentProps进行合并 this.updateState() } shouldComponentUpdate(nextProps, nextState) { // 进行判断,当数据产生扭转时,Component从新渲染 if (propsChanged || mapStateProducedChange || dispatchPropsChanged) { this.updateState(nextProps) return true } } componentDidMount() { // 扭转Component的state this.store.subscribe(() = { this.setState({ storeState: this.store.getState() }) }) } render() { // 生成包裹组件Connect return ( <WrappedComponent {...this.nextState} /> ) } } Connect.contextTypes = { store: storeShape } return Connect; } }
参考 前端进阶面试题具体解答
redux 有什么毛病
- 一个组件所须要的数据,必须由父组件传过来,而不能像 flux 中间接从 store 取
- 当一个组件相干数据更新时,即便父组件不须要用到这个组件,父组件还是会从新 render,可能会有效率影响,或者须要写简单的
shouldComponentUpdate
进行判断
react 的虚构dom是怎么实现的
首先说说为什么要应用Virturl DOM
,因为操作实在DOM
的消耗的性能代价太高,所以react
外部应用js
实现了一套dom构造,在每次操作在和实在dom之前,应用实现好的diff算法,对虚构dom进行比拟,递归找出有变动的dom节点,而后对其进行更新操作。为了实现虚构DOM
,咱们须要把每一种节点类型形象成对象,每一种节点类型有本人的属性,也就是prop,每次进行diff
的时候,react
会先比拟该节点类型,如果节点类型不一样,那么react
会间接删除该节点,而后间接创立新的节点插入到其中,如果节点类型一样,那么会比拟prop
是否有更新,如果有prop
不一样,那么react
会断定该节点有更新,那么重渲染该节点,而后在对其子节点进行比拟,一层一层往下,直到没有子节点
哪些办法会触发 React 从新渲染?从新渲染 render 会做些什么?
(1)哪些办法会触发 react 从新渲染?
- setState()办法被调用
setState 是 React 中最罕用的命令,通常状况下,执行 setState 会触发 render。然而这里有个点值得关注,执行 setState 的时候不肯定会从新渲染。当 setState 传入 null 时,并不会触发 render。
class App extends React.Component { state = { a: 1 }; render() { console.log("render"); return ( <React.Fragement> <p>{this.state.a}</p> <button onClick={() => { this.setState({ a: 1 }); // 这里并没有扭转 a 的值 }} > Click me </button> <button onClick={() => this.setState(null)}>setState null</button> <Child /> </React.Fragement> ); }}
- 父组件从新渲染
只有父组件从新渲染了,即便传入子组件的 props 未发生变化,那么子组件也会从新渲染,进而触发 render
(2)从新渲染 render 会做些什么?
- 会对新旧 VNode 进行比照,也就是咱们所说的Diff算法。
- 对新旧两棵树进行一个深度优先遍历,这样每一个节点都会一个标记,在到深度遍历的时候,每遍历到一和个节点,就把该节点和新的节点树进行比照,如果有差别就放到一个对象外面
- 遍历差别对象,依据差别的类型,依据对应对规定更新VNode
React 的解决 render 的根本思维模式是每次一有变动就会去从新渲染整个利用。在 Virtual DOM 没有呈现之前,最简略的办法就是间接调用 innerHTML。Virtual DOM厉害的中央并不是说它比间接操作 DOM 快,而是说不论数据怎么变,都会尽量以最小的代价去更新 DOM。React 将 render 函数返回的虚构 DOM 树与老的进行比拟,从而确定 DOM 要不要更新、怎么更新。当 DOM 树很大时,遍历两棵树进行各种比对还是相当耗性能的,特地是在顶层 setState 一个渺小的批改,默认会去遍历整棵树。只管 React 应用高度优化的 Diff 算法,然而这个过程依然会损耗性能.
为什么React并不举荐优先思考应用Context?
- Context目前还处于试验阶段,可能会在前面的发行版本中有很大的变动,事实上这种状况曾经产生了,所以为了防止给今后降级带来大的影响和麻烦,不倡议在app中应用context。
- 只管不倡议在app中应用context,然而独有组件而言,因为影响范畴小于app,如果能够做到高内聚,不毁坏组件树之间的依赖关系,能够思考应用context
- 对于组件之间的数据通信或者状态治理,无效应用props或者state解决,而后再思考应用第三方的成熟库进行解决,以上的办法都不是最佳的计划的时候,在思考context。
- context的更新须要通过setState()触发,然而这并不是很牢靠的,Context反对跨组件的拜访,然而如果两头的子组件通过一些办法不影响更新,比方 shouldComponentUpdate() 返回false 那么不能保障Context的更新肯定能够应用Context的子组件,因而,Context的可靠性须要关注
react hooks,它带来了那些便当
- 代码逻辑聚合,逻辑复用
- HOC嵌套天堂
- 代替class
React 中通常应用 类定义 或者 函数定义 创立组件:
在类定义中,咱们能够应用到许多 React 个性,例如 state、 各种组件生命周期钩子等,然而在函数定义中,咱们却无能为力,因而 React 16.8 版本推出了一个新性能 (React Hooks),通过它,能够更好的在函数定义组件中应用 React 个性。
益处:
- 跨组件复用: 其实 render props / HOC 也是为了复用,相比于它们,Hooks 作为官网的底层 API,最为轻量,而且革新老本小,不会影响原来的组件层次结构和传说中的嵌套天堂;
- 类定义更为简单
- 不同的生命周期会使逻辑变得扩散且凌乱,不易保护和治理;
- 时刻须要关注this的指向问题;
- 代码复用代价高,高阶组件的应用常常会使整个组件树变得臃肿;
- 状态与UI隔离: 正是因为 Hooks 的个性,状态逻辑会变成更小的粒度,并且极容易被形象成一个自定义 Hooks,组件中的状态和 UI 变得更为清晰和隔离。
留神:
- 防止在 循环/条件判断/嵌套函数 中调用 hooks,保障调用程序的稳固;
- 只有 函数定义组件 和 hooks 能够调用 hooks,防止在 类组件 或者 一般函数 中调用;
- 不能在useEffect中应用useState,React 会报错提醒;
- 类组件不会被替换或废除,不须要强制革新类组件,两种形式能并存;
重要钩子
- 状态钩子 (useState): 用于定义组件的 State,其到类定义中this.state的性能;
// useState 只承受一个参数: 初始状态// 返回的是组件名和更改该组件对应的函数const [flag, setFlag] = useState(true);// 批改状态setFlag(false)// 下面的代码映射到类定义中:this.state = { flag: true }const flag = this.state.flagconst setFlag = (bool) => { this.setState({ flag: bool, })}
- 生命周期钩子 (useEffect):
类定义中有许多生命周期函数,而在 React Hooks 中也提供了一个相应的函数 (useEffect),这里能够看做componentDidMount、componentDidUpdate和componentWillUnmount的联合。
useEffect(callback, [source])承受两个参数
- callback: 钩子回调函数;
- source: 设置触发条件,仅当 source 产生扭转时才会触发;
- useEffect钩子在没有传入[source]参数时,默认在每次 render 时都会优先调用上次保留的回调中返回的函数,后再从新调用回调;
useEffect(() => { // 组件挂载后执行事件绑定 console.log('on') addEventListener() // 组件 update 时会执行事件解绑 return () => { console.log('off') removeEventListener() }}, [source]);// 每次 source 产生扭转时,执行后果(以类定义的生命周期,便于大家了解):// --- DidMount ---// 'on'// --- DidUpdate ---// 'off'// 'on'// --- DidUpdate ---// 'off'// 'on'// --- WillUnmount --- // 'off'
通过第二个参数,咱们便可模拟出几个罕用的生命周期:
- componentDidMount: 传入[]时,就只会在初始化时调用一次
const useMount = (fn) => useEffect(fn, [])
- componentWillUnmount: 传入[],回调中的返回的函数也只会被最终执行一次
const useUnmount = (fn) => useEffect(() => fn, [])
- mounted: 能够应用 useState 封装成一个高度可复用的 mounted 状态;
const useMounted = () => { const [mounted, setMounted] = useState(false); useEffect(() => { !mounted && setMounted(true); return () => setMounted(false); }, []); return mounted;}
- componentDidUpdate: useEffect每次均会执行,其实就是排除了 DidMount 后即可;
const mounted = useMounted() useEffect(() => { mounted && fn()})
- 其它内置钩子:
useContext
: 获取 context 对象
useReducer
: 相似于 Redux 思维的实现,但其并不足以代替 Redux,能够了解成一个组件外部的 redux:- 并不是长久化存储,会随着组件被销毁而销毁;
- 属于组件外部,各个组件是互相隔离的,单纯用它并无奈共享数据;
- 配合useContext`的全局性,能够实现一个轻量级的 Redux;(easy-peasy)
useCallback
: 缓存回调函数,防止传入的回调每次都是新的函数实例而导致依赖组件从新渲染,具备性能优化的成果;useMemo
: 用于缓存传入的 props,防止依赖的组件每次都从新渲染;useRef
: 获取组件的实在节点;useLayoutEffect
- DOM更新同步钩子。用法与useEffect相似,只是区别于执行工夫点的不同
- useEffect属于异步执行,并不会期待 DOM 真正渲染后执行,而useLayoutEffect则会真正渲染后才触发;
- 能够获取更新后的 state;
- 自定义钩子(useXxxxx): 基于 Hooks 能够援用其它 Hooks 这个个性,咱们能够编写自定义钩子,如下面的useMounted。又例如,咱们须要每个页面自定义题目:
function useTitle(title) { useEffect( () => { document.title = title; });}// 应用:function Home() { const title = '我是首页' useTitle(title) return ( <div>{title}</div> )}
React中发动网络申请应该在哪个生命周期中进行?为什么?
对于异步申请,最好放在componentDidMount中去操作,对于同步的状态扭转,能够放在componentWillMount中,个别用的比拟少。
如果认为在componentWillMount里发动申请能提前取得后果,这种想法其实是谬误的,通常componentWillMount比componentDidMount早不了多少微秒,网络上任何一点提早,这一点差别都可忽略不计。
react的生命周期: constructor() -> componentWillMount() -> render() -> componentDidMount()
下面这些办法的调用是有秩序的,由上而下顺次调用。
- constructor被调用是在组件筹备要挂载的最开始,此时组件尚未挂载到网页上。
- componentWillMount办法的调用在constructor之后,在render之前,在这办法里的代码调用setState办法不会触发从新render,所以它个别不会用来作加载数据之用。
- componentDidMount办法中的代码,是在组件曾经齐全挂载到网页上才会调用被执行,所以能够保证数据的加载。此外,在这办法中调用setState办法,会触发从新渲染。所以,官网设计这个办法就是用来加载内部数据用的,或解决其余的副作用代码。与组件上的数据无关的加载,也能够在constructor里做,但constructor是做组件state初绐化工作,并不是做加载数据这工作的,constructor里也不能setState,还有加载的工夫太长或者出错,页面就无奈加载进去。所以有副作用的代码都会集中在componentDidMount办法里。
总结:
- 跟服务器端渲染(同构)有关系,如果在componentWillMount外面获取数据,fetch data会执行两次,一次在服务器端一次在客户端。在componentDidMount中能够解决这个问题,componentWillMount同样也会render两次。
- 在componentWillMount中fetch data,数据肯定在render后能力达到,如果遗记了设置初始状态,用户体验不好。
- react16.0当前,componentWillMount可能会被执行屡次。
diff算法?
- 把树形构造依照层级合成,只比拟同级元素。
- 给列表构造的每个单元增加惟一的
key
属性,不便比拟。 React
只会匹配雷同class
的component
(这外面的class
指的是组件的名字)- 合并操作,调用
component
的setState
办法的时候,React
将其标记为 -dirty
.到每一个事件循环完结,React
查看所有标记dirty
的component
从新绘制. - 抉择性子树渲染。开发人员能够重写
shouldComponentUpdate
进步diff
的性能
Component, Element, Instance 之间有什么区别和分割?
- 元素: 一个元素
element
是一个一般对象(plain object),形容了对于一个DOM节点或者其余组件component
,你想让它在屏幕上出现成什么样子。元素element
能够在它的属性props
中蕴含其余元素(译注:用于造成元素树)。创立一个React元素element
老本很低。元素element
创立之后是不可变的。 - 组件: 一个组件
component
能够通过多种形式申明。能够是带有一个render()
办法的类,简略点也能够定义为一个函数。这两种状况下,它都把属性props
作为输出,把返回的一棵元素树作为输入。 - 实例: 一个实例
instance
是你在所写的组件类component class
中应用关键字this
所指向的货色(译注:组件实例)。它用来存储本地状态和响应生命周期事件很有用。
函数式组件(Functional component
)基本没有实例instance
。类组件(Class component
)有实例instance
,然而永远也不须要间接创立一个组件的实例,因为React帮咱们做了这些。
React中refs的作用是什么?有哪些利用场景?
Refs 提供了一种形式,用于拜访在 render 办法中创立的 React 元素或 DOM 节点。Refs 应该审慎应用,如下场景应用 Refs 比拟适宜:
- 解决焦点、文本抉择或者媒体的管制
- 触发必要的动画
- 集成第三方 DOM 库
Refs 是应用 React.createRef() 办法创立的,他通过 ref 属性附加到 React 元素上。
要在整个组件中应用 Refs,须要将 ref 在构造函数中调配给其实例属性:
class MyComponent extends React.Component { constructor(props) { super(props) this.myRef = React.createRef() } render() { return <div ref={this.myRef} /> }}
如何用 React构建( build)生产模式?
通常,应用 Webpack的 DefinePlugin办法将 NODE ENV设置为 production。这将剥离 propType验证和额定的正告。除此之外,还能够缩小代码,因为 React应用 Uglify的dead-code来打消开发代码和正文,这将大大减少包占用的空间。
概述一下 React中的事件处理逻辑。
为了解决跨浏览器兼容性问题, React会将浏览器原生事件( Browser Native Event)封装为合成事件( Synthetic Event)并传入设置的事件处理程序中。
这里的合成事件提供了与原生事件雷同的接口,不过它们屏蔽了底层浏览器的细节差别,保障了行为的一致性。另外, React并没有间接将事件附着到子元素上,而是以繁多事件监听器的形式将所有的事件发送到顶层进行解决(基于事件委托原理)。
这样 React在更新DOM时就不须要思考如何解决附着在DOM上的事件监听器,最终达到优化性能的目标。
说说 React组件开发中对于作用域的常见问题。
在 EMAScript5语法标准中,对于作用域的常见问题如下。
(1)在map等办法的回调函数中,要绑定作用域this(通过bind办法)。
(2)父组件传递给子组件办法的作用域是父组件实例化对象,无奈扭转。
(3)组件事件回调函数办法的作用域是组件实例化对象(绑定父组件提供的办法就是父组件实例化对象),无奈扭转。
在 EMAScript6语法标准中,对于作用域的常见问题如下。
(1)当应用箭头函数作为map等办法的回调函数时,箭头函数的作用域是以后组件的实例化对象(即箭头函数的作用域是定义时的作用域),毋庸绑定作用域。
(2)事件回调函数要绑定组件作用域。
(3)父组件传递办法要绑定父组件作用域。
总之,在 EMAScript6语法标准中,组件办法的作用域是能够扭转的。
React中能够在render拜访refs吗?为什么?
<> <span id="name" ref={this.spanRef}>{this.state.title}</span> <span>{ this.spanRef.current ? '有值' : '无值' }</span></>
不能够,render 阶段 DOM 还没有生成,无奈获取 DOM。DOM 的获取须要在 pre-commit 阶段和 commit 阶段:
refs的作用是什么,你在什么样的业务场景下应用refs
- 操作DOM,为什么操作DOM?
场景
- 图片渲染好后,操作图片宽高。比方做个放大镜性能