共计 8999 个字符,预计需要花费 23 分钟才能阅读完成。
概述一下 React 中的事件处理逻辑。
为了解决跨浏览器兼容性问题,React 会将浏览器原生事件(Browser Native Event)封装为合成事件(Synthetic Event)并传入设置的事件处理程序中。
这里的合成事件提供了与原生事件雷同的接口,不过它们屏蔽了底层浏览器的细节差别,保障了行为的一致性。另外,React 并没有间接将事件附着到子元素上,而是以繁多事件监听器的形式将所有的事件发送到顶层进行解决(基于事件委托原理)。
这样 React 在更新 DOM 时就不须要思考如何解决附着在 DOM 上的事件监听器,最终达到优化性能的目标。
如果用索引值作为 key 会呈现什么样的问题
-
若对数据进行逆序增加,逆序删除等毁坏程序的操作
则会产生没有必要的实在 DOM 更新,界面想过看不出区别,然而效劳低,性能不好
-
如果构造中还蕴含输出类的 DOM
会产生谬误的 DOM 更新 ===》界面会有问题
如果不存在对数据的逆序增加 逆序删除等毁坏程序操作,仅用于渲染展现,用 index 作为 key 也没有问题
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.flag
const setFlag = (bool) => {
this.setState({flag: bool,})
}
- 生命周期钩子 (useEffect):
类定义中有许多生命周期函数,而在 React Hooks 中也提供了一个相应的函数 (useEffect),这里能够看做 componentDidMount、componentDidUpdate 和 componentWillUnmount 的联合。
useEffect(callback,)承受两个参数
- callback: 钩子回调函数;
- source: 设置触发条件,仅当 source 产生扭转时才会触发;
- useEffect 钩子在没有传入参数时,默认在每次 render 时都会优先调用上次保留的回调中返回的函数,后再从新调用回调;
useEffect(() => {
// 组件挂载后执行事件绑定
console.log('on')
addEventListener()
// 组件 update 时会执行事件解绑
return () => {console.log('off')
removeEventListener()}
}, );
// 每次 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 Portal 有哪些应用场景
- 在以前,react 中所有的组件都会位于 #app 下,而应用 Portals 提供了一种脱离 #app 的组件
- 因而 Portals 适宜脱离文档流(out of flow) 的组件,特地是 position: absolute 与 position: fixed 的组件。比方模态框,告诉,正告,goTop 等。
以下是官网一个模态框的示例,能够在以下地址中测试成果
<html>
<body>
<div id="app"></div>
<div id="modal"></div>
<div id="gotop"></div>
<div id="alert"></div>
</body>
</html>
const modalRoot = document.getElementById('modal');
class Modal extends React.Component {constructor(props) {super(props);
this.el = document.createElement('div');
}
componentDidMount() {modalRoot.appendChild(this.el);
}
componentWillUnmount() {modalRoot.removeChild(this.el);
}
render() {
return ReactDOM.createPortal(
this.props.children,
this.el,
);
}
}
React Hooks 当中的 useEffect 是如何辨别生命周期钩子的
useEffect 能够看成是
componentDidMount
,componentDidUpdate
和componentWillUnmount
三者的联合。useEffect(callback,)接管两个参数,调用形式如下
useEffect(() => {console.log('mounted');
return () => {console.log('willUnmount');
}
}, );
生命周期函数的调用次要是通过第二个参数来进行管制,有如下几种状况:
参数不传时,则每次都会优先调用上次保留的函数中返回的那个函数,而后再调用内部那个函数;
参数传 [] 时,则内部的函数只会在初始化时调用一次,返回的那个函数也只会最终在组件卸载时调用一次;
参数有值时,则只会监听到数组中的值发生变化后才优先调用返回的那个函数,再调用内部的函数。
createElement 和 cloneElement 有什么区别?
createElement 是 JSX 被转载失去的,在 React 中用来创立 React 元素(即虚构 DOM)的内容。cloneElement 用于复制元素并传递新的 props。
redux 有什么毛病
- 一个组件所须要的数据,必须由父组件传过来,而不能像
flux
中间接从store
取。 - 当一个组件相干数据更新时,即便父组件不须要用到这个组件,父组件还是会从新
render
,可能会有效率影响,或者须要写简单的shouldComponentUpdate
进行判断。
形容事件在 React 中的解决形式。
为了解决跨浏览器兼容性问题,React 中的事件处理程序将传递 SyntheticEvent 的实例,它是跨浏览器事件的包装器。这些 SyntheticEvent 与你习惯的原生事件具备雷同的接口,它们在所有浏览器中都兼容。
React 实际上并没有将事件附加到子节点自身。而是通过事件委托模式,应用单个事件监听器监听顶层的所有事件。这对于性能是有益处的。这也意味着在更新 DOM 时,React 不须要放心跟踪事件监听器。
参考:前端 react 面试题具体解答
redux 中间件
中间件提供第三方插件的模式,自定义拦挡 action -> reducer 的过程。变为 action -> middlewares -> reducer。这种机制能够让咱们扭转数据流,实现如异步 action,action 过 滤,日志输入,异样报告等性能
常见的中间件:
- redux-logger: 提供日志输入;
- redux-thunk: 解决异步操作;
- redux-promise: 解决异步操作;
- actionCreator 的返回值是 promise
在 React 中元素(element)和组件(component)有什么区别?
简略地说,在 React 中元素(虛拟 DOM)形容了你在屏幕上看到的 DOM 元素。
换个说法就是,在 React 中元素是页面中 DOM 元素的对象示意形式。在 React 中组件是一个函数或一个类,它能够承受输出并返回一个元素。
留神:工作中,为了进步开发效率,通常应用 JSX 语法示意 React 元素(虚构 DOM)。在编译的时候,把它转化成一个 React. createElement 调用办法。
如何应用 4.0 版本的 React Router?
React Router 4.0 版本中对 hashHistory 做了迁徙,执行包装置命令 npm install react-router-dom 后,依照如下代码进行应用即可。
import {HashRouter, Route, Redirect, Switch} from "react-router-dom";
class App extends Component {render() {
return (
<div>
<Switch>
<Route path="/list" componen t={List}></Route>
<Route path="/detail/:id" component={Detail}>
{" "}
</Route>
<Redirect from="/" to="/list">
{" "}
</Redirect>
</Switch>
</div>
);
}
}
const routes = (
<HashRouter>
<App> </App>
</HashRouter>
);
render(routes, ickt);
key 的作用
是给每一个 vnode 的惟一 id, 能够依附 key, 更精确, 更快的拿到 oldVnode 中对应的 vnode 节点
<!-- 更新前 -->
<div>
<p key="ka">ka</p>
<h3 key="song">song</he>
</div>
<!-- 更新后 -->
<div>
<h3 key="song">song</h3>
<p key="ka">ka</p>
</div>
如果没有 key,React 会认为 div 的第一个子节点由 p 变成 h3,第二个子节点由 h3 变成 p,则会销毁这两个节点并从新结构。
然而当咱们用 key 指明了节点前后对应关系后,React 晓得 key === "ka"
的 p 更新后还在,所以能够复用该节点,只须要替换程序。
key 是 React 用来追踪哪些列表元素被批改、被增加或者被移除的辅助标记。
在开发过程中,咱们须要保障某个元素的 key 在其同级元素中具备唯一性。在 React diff 算法中,React 会借助元素的 Key 值来判断该元素是早先创立的还是被挪动而来的元素,从而缩小不必要的元素从新渲染。同时,React 还须要借助 key 来判断元素与本地状态的关联关系。
componentWillReceiveProps 调用机会
- 曾经被废除掉
- 当 props 扭转的时候才调用,子组件第二次接管到 props 的时候
如何用 React 构建(build)生产模式?
通常,应用 Webpack 的 DefinePlugin 办法将 NODE ENV 设置为 production。这将剥离 propType 验证和额定的正告。除此之外,还能够缩小代码,因为 React 应用 Uglify 的 dead-code 来打消开发代码和正文,这将大大减少包占用的空间。
react 旧版生命周期函数
初始化阶段
getDefaultProps
: 获取实例的默认属性getInitialState
: 获取每个实例的初始化状态componentWillMount
:组件行将被装载、渲染到页面上render
: 组件在这里生成虚构的DOM
节点componentDidMount
: 组件真正在被装载之后
运行中状态
componentWillReceiveProps
: 组件将要接管到属性的时候调用shouldComponentUpdate
: 组件承受到新属性或者新状态的时候(能够返回 false,接收数据后不更新,阻止render
调用,前面的函数不会被继续执行了)componentWillUpdate
: 组件行将更新不能批改属性和状态render
: 组件从新描述componentDidUpdate
: 组件曾经更新
销毁阶段
componentWillUnmount
: 组件行将销毁
在 React 中遍历的办法有哪些?
(1)遍历数组:map && forEach
import React from 'react';
class App extends React.Component {render() {let arr = ['a', 'b', 'c', 'd'];
return (
<ul>
{arr.map((item, index) => {return <li key={index}>{item}</li>
})
}
</ul>
)
}
}
class App extends React.Component {render() {let arr = ['a', 'b', 'c', 'd'];
return (
<ul>
{arr.forEach((item, index) => {return <li key={index}>{item}</li>
})
}
</ul>
)
}
}
(2)遍历对象:map && for in
class App extends React.Component {render() {
let obj = {
a: 1,
b: 2,
c: 3
}
return (
<ul>
{(() => {let domArr = [];
for(const key in obj) {if(obj.hasOwnProperty(key)) {const value = obj[key]
domArr.push(<li key={key}>{value}</li>)
}
}
return domArr;
})()}
</ul>
)
}
}
// Object.entries() 把对象转换成数组
class App extends React.Component {render() {
let obj = {
a: 1,
b: 2,
c: 3
}
return (
<ul>
{Object.entries(obj).map(([key, value], index) => {// item 是一个数组,把 item 解构,写法是[key, value]
return <li key={key}>{value}</li>
})
}
</ul>
)
}
}
React-Router 的实现原理是什么?
客户端路由实现的思维:
-
基于 hash 的路由:通过监听
hashchange
事件,感知 hash 的变动- 扭转 hash 能够间接通过 location.hash=xxx
-
基于 H5 history 路由:
- 扭转 url 能够通过 history.pushState 和 resplaceState 等,会将 URL 压入堆栈,同时可能利用
history.go()
等 API - 监听 url 的变动能够通过自定义事件触发实现
- 扭转 url 能够通过 history.pushState 和 resplaceState 等,会将 URL 压入堆栈,同时可能利用
react-router 实现的思维:
- 基于
history
库来实现上述不同的客户端路由实现思维,并且可能保留历史记录等,磨平浏览器差别,下层无感知 - 通过保护的列表,在每次 URL 发生变化的回收,通过配置的 路由门路,匹配到对应的 Component,并且 render
React 16.X 中 props 扭转后在哪个生命周期中解决
在 getDerivedStateFromProps 中进行解决。
这个生命周期函数是为了代替 componentWillReceiveProps
存在的,所以在须要应用 componentWillReceiveProps
时,就能够思考应用 getDerivedStateFromProps
来进行代替。
两者的参数是不雷同的,而 getDerivedStateFromProps
是一个动态函数,也就是这个函数不能通过 this 拜访到 class 的属性,也并不举荐间接拜访属性。而是应该通过参数提供的 nextProps 以及 prevState 来进行判断,依据新传入的 props 来映射到 state。
须要留神的是,如果 props 传入的内容不须要影响到你的 state,那么就须要返回一个 null,这个返回值是必须的,所以尽量将其写到函数的开端:
static getDerivedStateFromProps(nextProps, prevState) {const {type} = nextProps;
// 当传入的 type 发生变化的时候,更新 state
if (type !== prevState.type) {
return {type,};
}
// 否则,对于 state 不进行任何操作
return null;
}
为什么虚构 dom 会进步性能
虚构 dom 相当于在 js 和实在 dom 两头加了一个缓存,利用 dom diff 算法防止了没有必要 的 dom 操作,从而进步性能
具体实现步骤如下:
- 用 JavaScript 对象构造示意 DOM 树的构造; 而后用这个树构建一个真正的 DOM 树,插到文档当中;
- 当状态变更的时候,从新结构一棵新的对象树。而后用新的树和旧的树进行比拟,记 录两棵树差别;
- 把 2 所记录的差别利用到步骤 1 所构建的真正的 DOM 树上,视图就更新了。
setState 办法的第二个参数有什么用?应用它的目标是什么?
它是一个回调函数,当 setState 办法执行完结并从新渲染该组件时调用它。在工作中,更好的形式是应用 React 组件生命周期之——“存在期”的生命周期办法,而不是依赖这个回调函数。
export class App extends Component {constructor(props) {super(props);
this.state = {username: "雨夜清荷",};
}
render() {return <div> {this.state.username}</div>;
}
componentDidMount() {
this.setstate(
{username: "有课前端网",},
() => console.log("re-rendered success.")
);
}
}
React 中 setState 的第二个参数作用是什么?
setState
的第二个参数是一个可选的回调函数。这个回调函数将在组件从新渲染后执行。等价于在 componentDidUpdate
生命周期内执行。通常倡议应用 componentDidUpdate
来代替此形式。在这个回调函数中你能够拿到更新后 state
的值:
this.setState({
key1: newState1,
key2: newState2,
...
}, callback) // 第二个参数是 state 更新实现后的回调函数