useEffect 与 useLayoutEffect 的区别
(1)共同点
- 使用成果: useEffect 与 useLayoutEffect 两者都是用于解决副作用,这些副作用包含扭转 DOM、设置订阅、操作定时器等。在函数组件外部操作副作用是不被容许的,所以须要应用这两个函数去解决。
- 应用形式: useEffect 与 useLayoutEffect 两者底层的函数签名是完全一致的,都是调用的 mountEffectImpl办法,在应用上也没什么差别,根本能够间接替换。
(2)不同点
- 应用场景: useEffect 在 React 的渲染过程中是被异步调用的,用于绝大多数场景;而 useLayoutEffect 会在所有的 DOM 变更之后同步调用,次要用于解决 DOM 操作、调整款式、防止页面闪动等问题。也正因为是同步解决,所以须要防止在 useLayoutEffect 做计算量较大的耗时工作从而造成阻塞。
- 应用成果: useEffect是依照程序执行代码的,扭转屏幕像素之后执行(先渲染,后扭转DOM),当扭转屏幕内容时可能会产生闪动;useLayoutEffect是扭转屏幕像素之前就执行了(会推延页面显示的事件,先扭转DOM后渲染),不会产生闪动。useLayoutEffect总是比useEffect先执行。
在将来的趋势上,两个 API 是会长期共存的,临时没有删减合并的打算,须要开发者依据场景去自行抉择。React 团队的倡议十分实用,如果切实分不清,先用 useEffect,个别问题不大;如果页面有异样,再间接替换为 useLayoutEffect 即可。
react 版本差别
react16.8 hooks
React 16之后有三个生命周期被废除(但并未删除)
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
官网打算在17版本齐全删除这三个函数,只保留UNSAVE_前缀的三个函数,目标是为了向下兼容,
react 16.4 新增
getSnapshotBeforeUpdate
getDerivedStateFromProps
对于废除的生命周期函数,官网会采纳逐渐迁徙的形式来实现版本的迁徙:
16.3:为不平安的生命周期引入别名,UNSAFE_componentWillMount、UNSAFE_componentWillReceiveProps 和 UNSAFE_componentWillUpdate。(旧的生命周期名称和新的别名都能够在此版本中应用。)
将来 16.x 版本:为 componentWillMount、componentWillReceiveProps 和 componentWillUpdate 启用废除告警。(旧的生命周期名称和新的别名都将在这个版本中工作,然而旧的名称在开发模式下会产生一个正告。)
17.0:删除 componentWillMount、componentWillReceiveProps 和 componentWillUpdate。(在此版本之后,只有新的 “UNSAFE_” 生命周期名称能够应用。)。
虚构 DOM 的引入与间接操作原生 DOM 相比,哪一个效率更高,为什么
虚构DOM绝对原生的DOM不肯定是效率更高,如果只批改一个按钮的文案,那么虚构 DOM 的操作无论如何都不可能比实在的 DOM 操作更快。在首次渲染大量DOM时,因为多了一层虚构DOM的计算,虚构DOM也会比innerHTML插入慢。它能保障性能上限,在实在DOM操作的时候进行针对性的优化时,还是更快的。所以要依据具体的场景进行探讨。
在整个 DOM 操作的演化过程中,其实主要矛盾并不在于性能,而在于开发者写得爽不爽,在于研发体验/研发效率。虚构 DOM 不是别的,正是前端开发们为了谋求更好的研发体验和研发效率而发明进去的高阶产物。虚构 DOM 并不一定会带来更好的性能,React 官网也素来没有把虚构 DOM 作为性能层面的卖点对外输入过。**虚构 DOM 的优越之处在于,它可能在提供更爽、更高效的研发模式(也就是函数式的 UI 编程形式)的同时,依然放弃一个还不错的性能。
React Hooks在平时开发中须要留神的问题和起因
(1)不要在循环,条件或嵌套函数中调用Hook,必须始终在 React函数的顶层应用Hook
这是因为React须要利用调用程序来正确更新相应的状态,以及调用相应的钩子函数。一旦在循环或条件分支语句中调用Hook,就容易导致调用程序的不一致性,从而产生难以预料到的结果。
(2)应用useState时候,应用push,pop,splice等间接更改数组对象的坑
应用push间接更改数组无奈获取到新值,应该采纳析构形式,然而在class外面不会有这个问题。代码示例:
function Indicatorfilter() { let [num,setNums] = useState([0,1,2,3]) const test = () => { // 这里坑是间接采纳push去更新num // setNums(num)是无奈更新num的 // 必须应用num = [...num ,1] num.push(1) // num = [...num ,1] setNums(num) }return ( <div className='filter'> <div onClick={test}>测试</div> <div> {num.map((item,index) => ( <div key={index}>{item}</div> ))} </div> </div> )}class Indicatorfilter extends React.Component<any,any>{ constructor(props:any){ super(props) this.state = { nums:[1,2,3] } this.test = this.test.bind(this) } test(){ // class采纳同样的形式是没有问题的 this.state.nums.push(1) this.setState({ nums: this.state.nums }) } render(){ let {nums} = this.state return( <div> <div onClick={this.test}>测试</div> <div> {nums.map((item:any,index:number) => ( <div key={index}>{item}</div> ))} </div> </div> ) }}
(3)useState设置状态的时候,只有第一次失效,前期须要更新状态,必须通过useEffect
TableDeail是一个公共组件,在调用它的父组件外面,咱们通过set扭转columns的值,认为传递给TableDeail 的 columns是最新的值,所以tabColumn每次也是最新的值,然而理论tabColumn是最开始的值,不会随着columns的更新而更新:
const TableDeail = ({ columns,}:TableData) => { const [tabColumn, setTabColumn] = useState(columns) }// 正确的做法是通过useEffect扭转这个值const TableDeail = ({ columns,}:TableData) => { const [tabColumn, setTabColumn] = useState(columns) useEffect(() =>{setTabColumn(columns)},[columns])}
(4)善用useCallback
父组件传递给子组件事件句柄时,如果咱们没有任何参数变动可能会选用useMemo。然而每一次父组件渲染子组件即便没变动也会跟着渲染一次。
(5)不要滥用useContext
能够应用基于 useContext 封装的状态管理工具。
setState之后 产生了什么?
- (1)代码中调用 setState 函数之后,React 会将传入的参数对象与组件以后的状态合并,而后触发所谓的和谐过程(Reconciliation)。
- (2)通过和谐过程,React 会以绝对高效的形式依据新的状态构建 React 元素树并且着手从新渲染整个 UI 界面;
- (3)在 React 失去元素树之后,React 会主动计算出新的树与老树的节点差别,而后依据差别对界面进行最小化重渲染;
- (4)在差别计算算法中,React 可能绝对准确地晓得哪些地位产生了扭转以及应该如何扭转,这就保障了按需更新,而不是全副从新渲染。
setState的调用会引起React的更新生命周期的4个函数执行。
shouldComponentUpdate
componentWillUpdate
render
componentDidUpdate
说说你用react有什么坑点?
1. JSX做表达式判断时候,须要强转为boolean类型
如果不应用!!b
进行强转数据类型,会在页面外面输入0
。
render() { const b = 0; return <div> { !!b && <div>这是一段文本</div> } </div>}
2. 尽量不要在 componentWillReviceProps
里应用 setState,如果肯定要应用,那么须要判断完结条件,不然会呈现有限重渲染,导致页面解体
3. 给组件增加ref时候,尽量不要应用匿名函数,因为当组件更新的时候,匿名函数会被当做新的prop解决,让ref属性承受到新函数的时候,react外部会先清空ref,也就是会以null为回调参数先执行一次ref这个props,而后在以该组件的实例执行一次ref,所以用匿名函数做ref的时候,有的时候去ref赋值后的属性会取到null
4. 遍历子节点的时候,不要用 index 作为组件的 key 进行传入
参考 前端进阶面试题具体解答
React 高阶组件、Render props、hooks 有什么区别,为什么要一直迭代
这三者是目前react解决代码复用的次要形式:
- 高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 本身不是 React API 的一部分,它是一种基于 React 的组合个性而造成的设计模式。具体而言,高阶组件是参数为组件,返回值为新组件的函数。
- render props是指一种在 React 组件之间应用一个值为函数的 prop 共享代码的简略技术,更具体的说,render prop 是一个用于告知组件须要渲染什么内容的函数 prop。
- 通常,render props 和高阶组件只渲染一个子节点。让 Hook 来服务这个应用场景更加简略。这两种模式仍有用武之地,(例如,一个虚构滚动条组件或者会有一个 renderltem 属性,或是一个可见的容器组件或者会有它本人的 DOM 构造)。但在大部分场景下,Hook 足够了,并且可能帮忙缩小嵌套。
(1)HOC 官网解释∶
高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 本身不是 React API 的一部分,它是一种基于 React 的组合个性而造成的设计模式。
简言之,HOC是一种组件的设计模式,HOC承受一个组件和额定的参数(如果须要),返回一个新的组件。HOC 是纯函数,没有副作用。
// hoc的定义function withSubscription(WrappedComponent, selectData) { return class extends React.Component { constructor(props) { super(props); this.state = { data: selectData(DataSource, props) }; } // 一些通用的逻辑解决 render() { // ... 并应用新数据渲染被包装的组件! return <WrappedComponent data={this.state.data} {...this.props} />; } };// 应用const BlogPostWithSubscription = withSubscription(BlogPost, (DataSource, props) => DataSource.getBlogPost(props.id));
HOC的优缺点∶
- 长处∶ 逻辑服用、不影响被包裹组件的外部逻辑。
- 毛病∶ hoc传递给被包裹组件的props容易和被包裹后的组件重名,进而被笼罩
(2)Render props 官网解释∶
"render prop"是指一种在 React 组件之间应用一个值为函数的 prop 共享代码的简略技术
具备render prop 的组件承受一个返回React元素的函数,将render的渲染逻辑注入到组件外部。在这里,"render"的命名能够是任何其余无效的标识符。
// DataProvider组件外部的渲染逻辑如下class DataProvider extends React.Components { state = { name: 'Tom' } render() { return ( <div> <p>共享数据组件本人外部的渲染逻辑</p> { this.props.render(this.state) } </div> ); }}// 调用形式<DataProvider render={data => ( <h1>Hello {data.name}</h1>)}/>
由此能够看到,render props的优缺点也很显著∶
- 长处:数据共享、代码复用,将组件内的state作为props传递给调用者,将渲染逻辑交给调用者。
- 毛病:无奈在 return 语句外拜访数据、嵌套写法不够优雅
(3)Hooks 官网解释∶
Hook是 React 16.8 的新增个性。它能够让你在不编写 class 的状况下应用 state 以及其余的 React 个性。通过自定义hook,能够复用代码逻辑。
// 自定义一个获取订阅数据的hookfunction useSubscription() { const data = DataSource.getComments(); return [data];}// function CommentList(props) { const {data} = props; const [subData] = useSubscription(); ...}// 应用<CommentList data='hello' />
以上能够看出,hook解决了hoc的prop笼罩的问题,同时应用的形式解决了render props的嵌套天堂的问题。hook的长处如下∶
- 应用直观;
- 解决hoc的prop 重名问题;
- 解决render props 因共享数据 而呈现嵌套天堂的问题;
- 能在return之外应用数据的问题。
须要留神的是:hook只能在组件顶层应用,不可在分支语句中应用。、
React.Component 和 React.PureComponent 的区别
PureComponent示意一个纯组件,能够用来优化React程序,缩小render函数执行的次数,从而进步组件的性能。
在React中,当prop或者state发生变化时,能够通过在shouldComponentUpdate生命周期函数中执行return false来阻止页面的更新,从而缩小不必要的render执行。React.PureComponent会主动执行 shouldComponentUpdate。
不过,pureComponent中的 shouldComponentUpdate() 进行的是浅比拟,也就是说如果是援用数据类型的数据,只会比拟不是同一个地址,而不会比拟这个地址外面的数据是否统一。浅比拟会疏忽属性和或状态渐变状况,其实也就是数据援用指针没有变动,而数据产生扭转的时候render是不会执行的。如果须要从新渲染那么就须要从新开拓空间援用数据。PureComponent个别会用在一些纯展现组件上。
应用pureComponent的益处:当组件更新时,如果组件的props或者state都没有扭转,render函数就不会触发。省去虚构DOM的生成和比照过程,达到晋升性能的目标。这是因为react主动做了一层浅比拟。
展现组件(Presentational component)和容器组件(Container component)之间有何不同
展现组件关怀组件看起来是什么。展现专门通过 props 承受数据和回调,并且简直不会有本身的状态,但当展现组件领有本身的状态时,通常也只关怀 UI 状态而不是数据的状态。
容器组件则更关怀组件是如何运作的。容器组件会为展现组件或者其它容器组件提供数据和行为(behavior),它们会调用 Flux actions
,并将其作为回调提供给展现组件。容器组件常常是有状态的,因为它们是(其它组件的)数据源。
概述一下 React中的事件处理逻辑。
为了解决跨浏览器兼容性问题, React会将浏览器原生事件( Browser Native Event)封装为合成事件( Synthetic Event)并传入设置的事件处理程序中。
这里的合成事件提供了与原生事件雷同的接口,不过它们屏蔽了底层浏览器的细节差别,保障了行为的一致性。另外, React并没有间接将事件附着到子元素上,而是以繁多事件监听器的形式将所有的事件发送到顶层进行解决(基于事件委托原理)。
这样 React在更新DOM时就不须要思考如何解决附着在DOM上的事件监听器,最终达到优化性能的目标。
请说岀 React从 EMAScript5编程标准到 EMAScript6编程标准过程中的几点扭转。
次要扭转如下。
(1)创立组件的办法不同。
EMAScript5版本中,定义组件用 React.createClass。EMAScript6版本中,定义组件要定义组件类,并继承 Component类。
(2)定义默认属性的办法不同。
EMAScript5版本中,用 getDefaultProps定义默认属性。EMAScript6版本中,为组件定义 defaultProps动态属性,来定义默认属性。
(3)定义初始化状态的办法不同。EMAScript5版本中,用 getInitialState定义初始化状态。EMAScript6版本中,在构造函数中,通过this. state定义初始化状态。
留神:构造函数的第一个参数是属性数据,肯定要用 super继承。
(4)定义属性束缚的办法不同。
EMAScript5版本中,用 propTypes定义属性的束缚。
EMAScript6版本中,为组件定义 propsTypes动态属性,来对属性进行束缚。
(5)应用混合对象、混合类的办法不同。
EMAScript5版本中,通过mixins继承混合对象的办法。
EMAScript6版本中,定义混合类,让混合类继承 Component类,而后让组件类继承混合类,实现对混合类办法的继承。
(6)绑定事件的办法不同。
EMAScript5版本中,绑定的事件回调函数作用域是组件实例化对象。
EMAScript6版本中,绑定的事件回调函数作用域是null。
(7)父组件传递办法的作用域不同。
EMAScript5版本中,作用域是父组件。 EMAScript6版本中,变成了null。
(8)组件办法作用域的批改办法不同。
EMAScript5版本中,无奈扭转作用域。
EMAScript6版本中,作用域是能够扭转的。
当调用 setState的时候,产生了什么操作?**
当调用 setState时, React做的第一件事是将传递给setState的对象合并到组件的以后状态,这将启动一个称为和解( reconciliation)的过程。
和解的最终目标是,依据这个新的状态以最无效的形式更新DOM。
为此, React将构建一个新的 React虚构DOM树(能够将其视为页面DOM元素的对象示意形式)。
一旦有了这个DOM树,为了弄清DOM是如何响应新的状态而扭转的, React会将这个新树与上一个虚构DOM树比拟。
这样做, React会晓得产生的确切变动,并且通过理解产生的变动后,在相对必要的状况下进行更新DOM,即可将因操作DOM而占用的空间最小化。
React中的setState和replaceState的区别是什么?
(1)setState() setState()用于设置状态对象,其语法如下:
setState(object nextState[, function callback])
- nextState,将要设置的新状态,该状态会和以后的state合并
- callback,可选参数,回调函数。该函数会在setState设置胜利,且组件从新渲染后调用。
合并nextState和以后state,并从新渲染组件。setState是React事件处理函数中和申请回调函数中触发UI更新的次要办法。
(2)replaceState() replaceState()办法与setState()相似,然而办法只会保留nextState中状态,原state不在nextState中的状态都会被删除。其语法如下:
replaceState(object nextState[, function callback])
- nextState,将要设置的新状态,该状态会替换以后的state。
- callback,可选参数,回调函数。该函数会在replaceState设置胜利,且组件从新渲染后调用。
总结: setState 是批改其中的局部状态,相当于 Object.assign,只是笼罩,不会缩小原来的状态。而replaceState 是齐全替换原来的状态,相当于赋值,将原来的 state 替换为另一个对象,如果新状态属性缩小,那么 state 中就没有这个状态了。
受控组件和非受控组件区别是啥?
- 受控组件是 React 管制中的组件,并且是表单数据实在的惟一起源。
- 非受控组件是由 DOM 解决表单数据的中央,而不是在 React 组件中。
只管非受控组件通常更易于实现,因为只需应用refs
即可从 DOM 中获取值,但通常倡议优先选择受管制的组件,而不是非受管制的组件。
这样做的次要起因是受控组件反对即时字段验证,容许有条件地禁用/启用按钮,强制输出格局。
React 中 refs 的作用是什么
Refs
是React
提供给咱们的平安拜访DOM
元素或者某个组件实例的句柄- 能够为元素增加
ref
属性而后在回调函数中承受该元素在DOM
树中的句柄,该值会作为回调函数的第一个参数返回
useEffect(fn, []) 和 componentDidMount 有什么差别
useEffect
会捕捉props
和 state。所以即使在回调函数里,你拿到的还是初始的 props 和 state。如果想得到“最新”的值,能够应用 ref。
Redux 中间件是什么?承受几个参数?柯里化函数两端的参数具体是什么?
Redux 的中间件提供的是位于 action 被发动之后,达到 reducer 之前的扩大点,换而言之,本来 view -→> action -> reducer -> store 的数据流加上中间件后变成了 view -> action -> middleware -> reducer -> store ,在这一环节能够做一些"副作用"的操作,如异步申请、打印日志等。
applyMiddleware源码:
export default function applyMiddleware(...middlewares) { return createStore => (...args) => { // 利用传入的createStore和reducer和创立一个store const store = createStore(...args) let dispatch = () => { throw new Error() } const middlewareAPI = { getState: store.getState, dispatch: (...args) => dispatch(...args) } // 让每个 middleware 带着 middlewareAPI 这个参数别离执行一遍 const chain = middlewares.map(middleware => middleware(middlewareAPI)) // 接着 compose 将 chain 中的所有匿名函数,组装成一个新的函数,即新的 dispatch dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } }}
从applyMiddleware中能够看出∶
- redux中间件承受一个对象作为参数,对象的参数上有两个字段 dispatch 和 getState,别离代表着 Redux Store 上的两个同名函数。
- 柯里化函数两端一个是 middewares,一个是store.dispatch
react-redux 的实现原理?
通过 redux 和 react context 配合应用,并借助高阶函数,实现了 react-redux
diff 虚构DOM 比拟的规定
【旧虚构DOM】 与 【新虚构DOM】中雷同key
若虚构DOM中的内容没有产生扭转,间接应用旧的虚构DOM
若虚构DOM中的内容产生扭转了,则生成新实在的DOM,随后替换页面中之前的实在DOM
【旧虚构DOM】 中未找到 与 【新虚构DOM】雷同的key
依据数据创立实在DOM,随后渲染到页面
类组件和函数组件之间的区别是啥?
- 类组件能够应用其余个性,如状态
state
和生命周期钩子。 当组件只是接管
props
渲染到页面时,就是无状态组件,就属于函数组件,也被称为哑组件或展现组件。
函数组件和类组件当然是有区别的,而且函数组件的性能比类组件的性能要高,因为类组件应用的时候要实例化,而函数组件间接执行函数取返回后果即可。为了进步性能,尽量应用函数组件。区别 函数组件 类组件 是否有 this
没有 有 是否有生命周期 没有 有 是否有状态 state
没有 有