共计 16923 个字符,预计需要花费 43 分钟才能阅读完成。
发问:react 我的项目中的 JSX 里,onChange={this.func.bind(this)}的写法,为什么要比非 bind 的 func = () => {}的写法效率高?
申明: 因为自己程度无限,有考虑不周之处,或者呈现谬误的,请严格指出,小弟感激不尽。这是小弟第一篇文章,有啥潜规则不懂的,你们就通知我。小弟今天有分享,等分享完了之后,持续欠缺。
之前不经意间看到这道题,据说是阿里 p5-p6 级别的题目,咱们先看一下这道题目,明面上是考查对 react 的理解深度,实际上波及的考点很多:bind,arrow function,react 各种绑定 this 的办法,优缺点,适宜的场景,类的继承,原型链等等,所以综合性很强。
咱们明天的主题就是由此题目,来总结一下相干的知识点,这里我会 着重剖析题目中第二种绑定计划。
五种 this 绑定计划的差异性
计划一: React.createClass
这是老版本 React 中用来申明组件的形式,在那个版本,没有引入 class 这种概念,所以通过这种形式来创立一个组件类(constructor)
ES6 的 class 相比 createClass,移除了两点:一个是 mixin 一个是 this 的主动绑定。前者能够用 HOC 代替,后者则是完完全全的没有,起因是 FB 认为这样能够防止和 JS 的语法产生混同,所以去掉了。
应用这种办法,咱们不须要放心 this,它会主动绑定到组件实例身上,然而这个 API 曾经废除了,所以只须要理解。
const App = React.createClass({handleClick() {console.log(this)
},
render() {return <div onClick={this.handleClick}> 你好 </div>
}
})
计划二:在 render 函数中应用 bind
class Test extends Component {handleClick() {console.log(this)
}
render() {return <div onClick={this.handleClick.bind(this)}></div>
}
}
计划三:在 render 函数中应用箭头函数
class Test extends Component {handleClick() {console.log(this)
}
render() {return <div onClick={() => this.handleClick()}></div>
}
}
这两个计划简洁明了, 能够传参,然而也存在潜在的性能问题: 会引起不必要的渲染
咱们经常会在代码中看到这些场景: 更多演示案例请点击
class Test extends Component {render() {
return <div>
<Input />
<button> 增加 <button>
<List options={this.state.options || Immutable.Map()} data={this.state.data} onSelect={this.onSelect.bind(this)} /> // 1 pureComponent
</div>
}
}
场景一:应用空对象 / 数组来做兜底计划,防止 options 没有数据时运行时报错。
场景二:应用箭头函数来绑定 this。
可能在一些不须要关怀性能的场景下这两种写法没有什么太大的害处,然而如果咱们正在思考性能优化,譬如咱们应用了 PureComponent
来去优化咱们的渲染性能
这外面 React 有应用 shallowEqual 做第一层的比拟,这个时候咱们关注的可能是这个 data(数据是否有变动从而影响渲染),然而被咱们漠视的 options,onSelect 却会间接导致 PureComponent 生效,然而咱们找不到优化失败的起因。
而假如咱们的外围 data 是 Immutable
的,这样其实优化了咱们做 diff 相干的性能。当 data 为 null 时,此时咱们冀望的是不会反复渲染,然而当咱们的 Test 组件有状态更新,触发了 Test 的从新渲染,此时 render 执行,List 依旧会从新渲染。起因就是 咱们每次执行 render,传递给子组件的 options,onSelect 是一个新的对象 / 函数。
这样在做 shallowEqual 时,会认为有更新,所以会更新 List 组件。
这个中央也有很多解决方案:
- 不要间接在 render 函数外面做兜底,或者应用同一援用的数据源
- 对于事件监听函数,咱们能够当时做好绑定,应用计划 4 或者 5,或者最新的 hook(useCallback、useMemo)
const onSelect = useCallback(() => {... // 和 select 相干的逻辑}, []) // 第二个参数是相干的依赖,只有依赖变了,onSelect 才会变,设置为空数组,示意永远不变
计划四:在构造函数中应用 bind
class Test extends Component {constrcutor() {this.handleClick = this.handleClick.bind(this)
}
handleClick() {console.log(this)
}
render() {return <Button onClick={this.handleClick}> 测试 </Button>
}
}
这种计划是 React 举荐的形式,只在实例化组件的时候做一次绑定,之后传递的都是同一援用,没有计划二、三带来的负面效应。
然而这种写法绝对 2,3 繁琐了许多:
1. 如果咱们并不需要在构造函数里做什么的话,为了做函数绑定,咱们须要手动申明构造函数;这里没有思考到实例属性的新写法,间接在顶层赋值。感激 @Yes 好 2012 斧正。
- 针对一些简单的组件(要绑定的办法过多),咱们须要多次重复的去写这些办法名;
- 无奈独自解决传参问题(这一点尤其重要,也限度了它的应用场景)。
计划五:应用箭头函数定义方法(class properties)
这种技术依赖于 Class Properties
提案,目前还在 stage-2
阶段,如果须要应用这种计划,咱们须要装置@babel/plugin-proposal-class-properties
class Test extends Component {handleClick = () => {console.log(this)
}
render() {return <button onClick={this.handleClick}> 测试 </button>
}
}
这也是咱们面试题中提到的第二种绑定计划
先总结一下长处:
- 主动绑定
- 没有计划二、三所带来的渲染性能问题(只绑定一次,没有生成新的函数);
- 能够再封装一下,应用
params => () => {}
这种写法来达到传参的目标。
咱们在 babel 上做一下编译:点击 class-properties(抉择 ES2016 或者更高,须要手动装置一下这个 pluginbabel-plugin-transform-class-properties
相比于 @babel/plugin-proposal-class-properties
更直观,前者是 babel6 命名形式,后者是 babel7)
在应用 plugin 编译后的版本咱们能够看到,这种计划其实就是间接在构造函数中定义了一个 change 属性,而后赋值为箭头函数,从而实现的对 this 的绑定,看起来很完满,很精妙。然而,正是因为这种写法,意味着由这个组件类实例化的所有组件实例都会调配一块内存来去存储这个箭头函数。而咱们定义的一般办法,其实是定义在原型对象上的,被所有实例共享,就义的代价则是须要咱们应用 bind 手动绑定,生成了一个新的函数。
咱们看一下 bind 函数的 polyfill:
if (!Function.prototype.bind) {
... // do sth
var fBound = function() {
// this instanceof fBound === true 时, 阐明返回的 fBound 被当做 new 的结构函数调用
return fToBind.apply(this instanceof fBound
? this
: oThis,
// 获取调用时 (fBound) 的传参.bind 返回的函数入参往往是这么传递的
aArgs.concat(Array.prototype.slice.call(arguments)));
};
... // do sth
return fBound;
};
}
如果在不反对 bind 的浏览器上,其实编译后,也就相当于新生成的函数的函数体就一条语句: fToBind.apply(...)
。
咱们以图片的模式看一下差距:
注: 图中,虚线框面积代表援用函数所节俭的内存,实线框的面积代表耗费的内存。
图一:应用箭头函数做 this 绑定。只有 render 函数定义在原型对象上,由所有实例对象共享。其余内存耗费都是基于每个实例上的。
图二:在构造函数中做 this 绑定。render,handler 都定义在原型对象上,实例上的 handler 实线框代表应用 bind 生成的函数所耗费的内存大小。
如果咱们的 handler 函数体自身就很小,实例数量不多,绑定的办法不多。两种计划在内存占用上的差异性不大,然而一旦咱们 要在 handler 里解决简单的逻辑
,或者该 组件可能会产生大量的实例
,抑或是该 组件有大量的须要绑定办法
,第一种的劣势就突显进去了。
如果说下面这种绑定 this 的计划只用在 React 上,可能咱们只须要思考下面几点,然而如果咱们应用下面的办法去创立一些工具类,可能留神的不止这些。
说到类,可能大家都会想到类的继承,如果咱们须要重写某个基类的办法,运行上面,你会发现,和设想中的相差甚远。
class Base {sayHello() {console.log('Hello')
}
sayHey = () => {console.log('Hey')
}
}
class A extends Base {constructor() {super()
this.name = 'Bitch'
}
sayHey() {console.log('Hey', this.name)
}
}
new A().sayHello() // 'Hello'
new A().sayHey() // 'Hey'
注: 咱们心愿打印出 ‘Hello’ ‘Hey Bitch’,理论打印的是:’Hello’ ‘Hey’
起因很简略,在 A 的构造函数内,咱们调用 super 执行了 Base 的构造函数,向 A 实例上增加属性,这个时候执行 Base 构造函数后,A 实例上曾经有了 sayHey 属性,它的值是一个箭头函数,打印出·Hey·
而咱们重写的 sayHey 其实是定义在原型对象上的。所以最终执行的是在 Base 里定义的 sayHey 办法,但不是同一个办法。
据此,咱们还能够推理一下假如咱们要先执行 Base 的 sayHey,而后在此基础上执减少逻辑咱们又该怎么做?上面这种计划必定是行不通的。
sayHey() {super.sayHey() // 报错
console.log('get off!')
}
多说一句:有大佬认为这种办法的性能并不好,它考查的点是 ops/s(每秒能够实例化多少个组件,越多越好),最终得出的论断是
然而就有人提出质疑,这些办法咱们最终都会通过 babel 编译成浏览器能辨认的代码,那么最终运行的版本所体现的差异性是否可能代表其实在的差异性。具体的我也没细看,有须要理解更多的,能够
看一下这篇文章 Arrow Functions in Class Properties Might Not Be As Great As We Think
据此,咱们曾经 cover 了这道题少数考点,如果下次碰到这种题,或者想出这类题无妨从上面的角度去思考下
- 面试者的角度:
1.1 在答复这道题之前,写解释两种计划的原理,显然,面试官想要着重考查的是第二种的理解状况,他背地到底做了什么。而后谈谈他们一些惯例的优缺点
1.2 答复对于效率的问题,前者每次 bind,都会生成一个新的函数,然而函数体内代码量少,最重要的还是援用的原型上的 handler, 这个是共享的。然而前面这一种,他会在每个实例上生成一个函数,如果实例数量多,或者函数体大,或者是绑定函数过多,那么占用的内存就显著要超出第一种。 - 面试官的角度: 考 bind 实现,考 react 的绑定策略,优缺点,考性能优化策略,考箭头函数,考原型链,考继承。发散开来,真的很广。
总结:
每种绑定计划既然存在就有其存在的理由(除了第一种曾经是过来),然而也会有相应的弊病,并没有相对的谁好谁差,咱们在应用时,能够依据理论场景做抉择。
这道题目答到点不难,怎么让面试官感觉你懂得全面还是挺难的。
其次针对 this 绑定计划,** 如果特地在意性能,就义一点代码量,可读性:举荐四其次,如果本人自身够仔细,二三也能够应用,然而肯定要留神新生成的函数是否会导致多余渲染;
如果想不加班:举荐五(如何传参文章中有提及)。**
- 减少
shouldComponentUpdate
钩子对新旧props
进行比拟,如果值雷同则阻止更新,防止不必要的渲染,或者应用PureReactComponent
代替Component
,其外部曾经封装了shouldComponentUpdate
的浅比拟逻辑 - 对于列表或其余构造雷同的节点,为其中的每一项减少惟一
key
属性,以不便React
的diff
算法中对该节点的复用,缩小节点的创立和删除操作 render
函数中缩小相似onClick={() => {doSomething()}}
的写法,每次调用 render 函数时均会创立一个新的函数,即便内容没有产生任何变动,也会导致节点没必要的重渲染,倡议将函数保留在组件的成员对象中,这样只会创立一次- 组件的
props
如果须要通过一系列运算后能力拿到最终后果,则能够思考应用reselect
库对后果进行缓存,如果 props 值未发生变化,则后果间接从缓存中拿,防止昂扬的运算代价 webpack-bundle-analyzer
剖析以后页面的依赖包,是否存在不合理性,如果存在,找到优化点并进行优化
前端 react 面试题具体解答
在调用 setState 之后产生了什么
-
状态合并,触发和谐:
setState 函数之后,会将传入的参数对象与以后的状态合并,而后登程调用过程
-
依据新的状态构建虚构 dom 树
通过和谐过程,react 会高效的依据新的状态构建虚构 DOM 树,筹备渲染整个 UI 页面
-
计算新老树节点差别,最小化渲染
得倒新的虚构 DOM 树后,会计算出新老树的节点差别,会依据差别对界面进行最小化渲染
-
按需更新
在差别话计算中,react 能够绝对精确的晓得哪些地位产生了扭转以及该如何扭转,这保障按需更新,而不是发表从新渲染
应用 React 有何长处
- 只需查看
render
函数就会很容易晓得一个组件是如何被渲染的 - JSX 的引入,使得组件的代码更加可读,也更容易看懂组件的布局,或者组件之间是如何相互援用的
- 反对服务端渲染,这能够改良 SEO 和性能
- 易于测试
- React 只关注 View 层,所以能够和其它任何框架 (如 Backbone.js, Angular.js) 一起应用
HOC 相比 mixins 有什么长处?
HOC 和 Vue 中的 mixins 作用是统一的,并且在晚期 React 也是应用 mixins 的形式。然而在应用 class 的形式创立组件当前,mixins 的形式就不能应用了,并且其实 mixins 也是存在一些问题的,比方:
- 隐含了一些依赖,比方我在组件中写了某个
state
并且在mixin
中应用了,就这存在了一个依赖关系。万一下次他人要移除它,就得去mixin
中查找依赖 - 多个
mixin
中可能存在雷同命名的函数,同时代码组件中也不能呈现雷同命名的函数,否则就是重写了,其实我始终感觉命名真的是一件麻烦事。。 - 雪球效应,尽管我一个组件还是应用着同一个
mixin
,然而一个mixin
会被多个组件应用,可能会存在需要使得mixin
批改本来的函数或者新增更多的函数,这样可能就会产生一个保护老本
HOC 解决了这些问题,并且它们达成的成果也是统一的,同时也更加的政治正确(毕竟更加函数式了)。
为什么应用 jsx 的组件中没有看到应用 react 却须要引入 react?
实质上来说 JSX 是 React.createElement(component, props, ...children)
办法的语法糖。在 React 17 之前,如果应用了 JSX,其实就是在应用 React,babel
会把组件转换为 CreateElement
模式。在 React 17 之后,就不再须要引入,因为 babel
曾经能够帮咱们主动引入 react。
hooks 父子传值
父传子
在父组件中用 useState 申明数据
const [data, setData] = useState(false)
把数据传递给子组件
<Child data={data} />
子组件接管
export default function (props) {const { data} = props
console.log(data)
}
子传父
子传父能够通过事件办法传值,和父传子有点相似。在父组件中用 useState 申明数据
const [data, setData] = useState(false)
把更新数据的函数传递给子组件
<Child setData={setData} />
子组件中触发函数更新数据,就会间接传递给父组件
export default function (props) {const { setData} = props
setData(true)
}
如果存在多个层级的数据传递,也可按照此办法顺次传递
// 多层级用 useContext
const User = () => {
// 间接获取,不必回调
const {user, setUser} = useContext(UserContext);
return <Avatar user={user} setUser={setUser} />;
};
componentWillReceiveProps 调用机会
- 曾经被废除掉
- 当 props 扭转的时候才调用,子组件第二次接管到 props 的时候
为什么 React 要用 JSX?
JSX 是一个 JavaScript 的语法扩大,或者说是一个相似于 XML 的 ECMAScript 语法扩大。它自身没有太多的语法定义,也不冀望引入更多的规范。
其实 React 自身并不强制应用 JSX。在没有 JSX 的时候,React 实现一个组件依赖于应用 React.createElement 函数。代码如下:
class Hello extends React.Component {render() {
return React.createElement(
'div',
null,
`Hello ${this.props.toWhat}`
);
}
}
ReactDOM.render(React.createElement(Hello, {toWhat: 'World'}, null),
document.getElementById('root')
);
而 JSX 更像是一种语法糖,通过相似 XML 的形容形式,刻画函数对象。在采纳 JSX 之后,这段代码会这样写:
class Hello extends React.Component {render() {return <div>Hello {this.props.toWhat}</div>;
}
}
ReactDOM.render(
<Hello toWhat="World" />,
document.getElementById('root')
);
通过比照,能够清晰地发现,代码变得更为简洁,而且代码构造档次更为清晰。
因为 React 须要将组件转化为虚构 DOM 树,所以在编写代码时,实际上是在手写一棵构造树。而XML 在树结构的形容上天生具备可读性强的劣势。
但这样可读性强的代码仅仅是给写程序的同学看的,实际上在运行的时候,会应用 Babel 插件将 JSX 语法的代码还原为 React.createElement 的代码。
总结: JSX 是一个 JavaScript 的语法扩大,构造相似 XML。JSX 次要用于申明 React 元素,但 React 中并不强制应用 JSX。即便应用了 JSX,也会在构建过程中,通过 Babel 插件编译为 React.createElement。所以 JSX 更像是 React.createElement 的一种语法糖。
React 团队并不想引入 JavaScript 自身以外的开发体系。而是心愿通过正当的关注点拆散放弃组件开发的纯正性。
fetch 封装
npm install whatwg-fetch --save // 适配其余浏览器
npm install es6-promise
export const handleResponse = (response) => {if (response.status === 403 || response.status === 401) {const oauthurl = response.headers.get('locationUrl');
if (!_.isEmpty(oauthUrl)) {
window.location.href = oauthurl;
return;
}
}
if (!response.ok) {return getErrorMessage(response).then(errorMessage => apiError(response.status, errorMessage));
}
if (isJson(response)) {return response.json();
}
if (isText(response)) {return response.text();
}
return response.blob();};
const httpRequest = {
request: ({method, headers, body, path, query,}) => {const options = {};
let url = path;
if (method) {options.method = method;}
if (headers) {options.headers = {...options.headers,...headers};
}
if (body) {options.body = body;}
if (query) {const params = Object.keys(query)
.map(k => `${k}=${query[k]}`)
.join('&');
url = url.concat(`?${params}`);
}
return fetch(url, Object.assign({}, options, {credentials: 'same-origin'})).then(handleResponse);
},
};
export default httpRequest;
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 封装的状态管理工具。
在哪个生命周期中你会收回 Ajax 申请?为什么?
Ajax 申请应该写在组件创立期的第五个阶段,即 componentDidMount 生命周期办法中。起因如下。
在创立期的其余阶段,组件尚未渲染实现。而在存在期的 5 个阶段,又不能确保生命周期办法肯定会执行(如通过 shouldComponentUpdate 办法优化更新等)。在销毀期,组件行将被销毁,申请数据变得无意义。因而在这些阶段发岀 Ajax 申请显然不是最好的抉择。
在组件尚未挂载之前,Ajax 申请将无奈执行结束,如果此时发出请求,将意味着在组件挂载之前更新状态(如执行 setState),这通常是不起作用的。
在 componentDidMount 办法中,执行 Ajax 即可保障组件曾经挂载,并且可能失常更新组件。
指出 (组件) 生命周期办法的不同
componentWillMount
— 多用于根组件中的应用程序配置componentDidMount
— 在这能够实现所有没有 DOM 就不能做的所有配置,并开始获取所有你须要的数据;如果须要设置事件监听,也能够在这实现componentWillReceiveProps
— 这个周期函数作用于特定的 prop 扭转导致的 state 转换shouldComponentUpdate
— 如果你放心组件适度渲染,shouldComponentUpdate
是一个改善性能的中央,因为如果组件接管了新的prop
,它能够阻止 (组件) 从新渲染。shouldComponentUpdate 应该返回一个布尔值来决定组件是否要从新渲染componentWillUpdate
— 很少应用。它能够用于代替组件的componentWillReceiveProps
和shouldComponentUpdate
(但不能拜访之前的 props)componentDidUpdate
— 罕用于更新 DOM,响应 prop 或 state 的扭转componentWillUnmount
— 在这你能够勾销网络申请,或者移除所有与组件相干的事件监听器
高阶组件
高阶函数:如果一个函数 承受一个或多个函数作为参数或者返回一个函数 就可称之为 高阶函数。
高阶组件:如果一个函数 承受一个或多个组件作为参数并且返回一个组件 就可称之为 高阶组件。
react 中的高阶组件
React 中的高阶组件次要有两种模式:属性代理 和反向继承。
属性代理 Proxy
- 操作
props
- 抽离
state
- 通过
ref
拜访到组件实例 - 用其余元素包裹传入的组件
WrappedComponent
反向继承
会发现其属性代理和反向继承的实现有些相似的中央,都是返回一个继承了某个父类的子类,只不过属性代理中继承的是 React.Component
,反向继承中继承的是传入的组件 WrappedComponent
。
反向继承能够用来做什么:
1. 操作 state
高阶组件中能够读取、编辑和删除 WrappedComponent
组件实例中的 state
。甚至能够减少更多的state
项,然而 十分不倡议这么做 因为这可能会导致 state
难以保护及治理。
function withLogging(WrappedComponent) {
return class extends WrappedComponent {render() {
return (
<div>;
<h2>;Debugger Component Logging...<h2>;
<p>;state:<p>;
<pre>;{JSON.stringify(this.state, null, 4)}<pre>;
<p>props:<p>;
<pre>{JSON.stringify(this.props, null, 4)}<pre>;
{super.render()}
<div>;
);
}
};
}
2. 渲染劫持(Render Highjacking)
条件渲染通过 props.isLoading 这个条件来判断渲染哪个组件。
批改由 render() 输入的 React 元素树
react 最新版本解决了什么问题,减少了哪些货色
React 16.x 的三大新个性 Time Slicing、Suspense、hooks
- Time Slicing(解决 CPU 速度问题)使得在执行工作的期间能够随时暂停,跑去干别的事件,这个个性使得 react 能在性能极其差的机器跑时,依然放弃有良好的性能
- Suspense(解决网络 IO 问题) 和 lazy 配合,实现异步加载组件。能暂停以后组件的渲染,当实现某件事当前再持续渲染,解决从 react 出世到当初都存在的「异步副作用」的问题,而且解决得非的优雅,应用的是 T 异步然而同步的写法,这是最好的解决异步问题的形式
- 提供了一个 内置函数 componentDidCatch,当有谬误产生时,能够敌对地展现 fallback 组件; 能够捕捉到它的子元素(包含嵌套子元素)抛出的异样; 能够复用谬误组件。
(1)React16.8 退出 hooks,让 React 函数式组件更加灵便,hooks 之前,React 存在很多问题:
- 在组件间复用状态逻辑很难
- 简单组件变得难以了解,高阶组件和函数组件的嵌套过深。
- class 组件的 this 指向问题
- 难以记忆的生命周期
hooks 很好的解决了上述问题,hooks 提供了很多办法
- useState 返回有状态值,以及更新这个状态值的函数
- useEffect 承受蕴含命令式,可能有副作用代码的函数。
- useContext 承受上下文对象(从 React.createContext 返回的值)并返回以后上下文值,
- useReducer useState 的代替计划。承受类型为(state,action)=> newState 的 reducer,并返回与 dispatch 办法配对的以后状态。
- useCalLback 返回一个回顾的 memoized 版本,该版本仅在其中一个输出产生更改时才会更改。纯函数的输入输出确定性 o useMemo 纯的一个记忆函数 o useRef 返回一个可变的 ref 对象,其 Current 属性被初始化为传递的参数,返回的 ref 对象在组件的整个生命周期内放弃不变。
- useImperativeMethods 自定义应用 ref 时公开给父组件的实例值
- useMutationEffect 更新兄弟组件之前,它在 React 执行其 DOM 扭转的同一阶段同步触发
- useLayoutEffect DOM 扭转后同步触发。应用它来从 DOM 读取布局并同步从新渲染
(2)React16.9
- 重命名 Unsafe 的生命周期办法。新的 UNSAFE_前缀将有助于在代码 review 和 debug 期间,使这些有问题的字样更突出
- 废除 javascrip: 模式的 URL。以 javascript: 结尾的 URL 非常容易蒙受攻打,造成安全漏洞。
- 废除 ”Factory” 组件。工厂组件会导致 React 变大且变慢。
- act()也反对异步函数,并且你能够在调用它时应用 await。
- 应用 <React.ProfiLer> 进行性能评估。在较大的利用中追踪性能回归可能会很不便
(3)React16.13.0
- 反对在渲染期间调用 setState,但仅实用于同一组件
- 可检测抵触的款式规定并记录正告
- 废除 unstable_createPortal,应用 CreatePortal
- 将组件堆栈增加到其开发正告中,使开发人员可能隔离 bug 并调试其程序,这能够分明地阐明问题所在,并更快地定位和修复谬误。
应用状态要留神哪些事件?
要留神以下几点。
- 不要间接更新状态
- 状态更新可能是异步的
- 状态更新要合并。
- 数据从上向下流动
**
React 与 Vue 的 diff 算法有何不同?
diff 算法是指生成更新补丁的形式,次要利用于虚构 DOM 树变动后,更新实在 DOM。所以 diff 算法肯定存在这样一个过程:触发更新 → 生成补丁 → 利用补丁。
React 的 diff 算法,触发更新的机会次要在 state 变动与 hooks 调用之后。此时触发虚构 DOM 树变更遍历,采纳了深度优先遍历算法。但传统的遍历形式,效率较低。为了优化效率,应用了分治的形式。将繁多节点比对转化为了 3 种类型节点的比对,别离是树、组件及元素,以此晋升效率。
- 树比对:因为网页视图中较少有跨层级节点挪动,两株虚构 DOM 树只对同一档次的节点进行比拟。
- 组件比对:如果组件是同一类型,则进行树比对,如果不是,则间接放入到补丁中。
- 元素比对:次要产生在同层级中,通过标记节点操作生成补丁,节点操作对应实在的 DOM 剪裁操作。
以上是经典的 React diff 算法内容。自 React 16 起,引入了 Fiber 架构。为了使整个更新过程可随时暂停复原,节点与树别离采纳了 FiberNode 与 FiberTree 进行重构。fiberNode 应用了双链表的构造,能够间接找到兄弟节点与子节点。整个更新过程由 current 与 workInProgress 两株树双缓冲实现。workInProgress 更新实现后,再通过批改 current 相干指针指向新节点。
Vue 的整体 diff 策略与 React 对齐,尽管不足工夫切片能力,但这并不意味着 Vue 的性能更差,因为在 Vue 3 初期引入过,前期因为收益不高移除掉了。除了高帧率动画,在 Vue 中其余的场景简直都能够应用防抖和节流去进步响应性能。
diff 算法?
- 把树形构造依照层级合成,只比拟同级元素
- 给列表构造的每个单元增加惟一的 key 属性,不便比拟
- React 只会匹配雷同 class 的 component(这外面的 class 指的是组件的名字)
- 合并操作,调用 component 的 setState 办法的时候, React 将其标记为 dirty. 到每一个 事件循环完结, React 查看所有标记 dirty 的 component 从新绘制.
- 抉择性子树渲染。开发人员能够重写 shouldComponentUpdate 进步 diff 的性能。
diff 算法如何比拟?
- 只对同级比拟,跨层级的 dom 不会进行复用
- 不同类型节点生成的 dom 树不同,此时会间接销毁老节点及子孙节点,并新建节点
- 能够通过 key 来对元素 diff 的过程提供复用的线索
- 单节点 diff
- 单点 diff 有如下几种状况:
- key 和 type 雷同示意能够复用节点
- key 不同间接标记删除节点,而后新建节点
- key 雷同 type 不同,标记删除该节点和兄弟节点,而后新创建节点
在 Redux 中应用 Action 要留神哪些问题?
在 Redux 中应用 Action 的时候,Action 文件里尽量放弃 Action 文件的污浊,传入什么数据就返回什么数据,最妤把申请的数据和 Action 办法分来到,以放弃 Action 的污浊。
React Hooks 和生命周期的关系?
函数组件 的实质是函数,没有 state 的概念的,因而 不存在生命周期 一说,仅仅是一个 render 函数 而已。
然而引入 Hooks 之后就变得不同了,它能让组件在不应用 class 的状况下领有 state,所以就有了生命周期的概念,所谓的生命周期其实就是 useState
、useEffect()
和 useLayoutEffect()
。
即:Hooks 组件(应用了 Hooks 的函数组件)有生命周期,而函数组件(未应用 Hooks 的函数组件)是没有生命周期的。
上面是具体的 class 与 Hooks 的 生命周期对应关系:
constructor
:函数组件不须要构造函数,能够通过调用**useState 来初始化 state**
。如果计算的代价比拟低廉,也能够传一个函数给useState
。
const [num, UpdateNum] = useState(0)
getDerivedStateFromProps
:个别状况下,咱们不须要应用它,能够在 渲染过程中更新 state,以达到实现getDerivedStateFromProps
的目标。
function ScrollView({row}) {let [isScrollingDown, setIsScrollingDown] = useState(false);
let [prevRow, setPrevRow] = useState(null);
if (row !== prevRow) {
// Row 自上次渲染以来产生过扭转。更新 isScrollingDown。setIsScrollingDown(prevRow !== null && row > prevRow);
setPrevRow(row);
}
return `Scrolling down: ${isScrollingDown}`;
}
React 会立刻退出第一次渲染并用更新后的 state 从新运行组件以防止消耗太多性能。
shouldComponentUpdate
:能够用**React.memo**
包裹一个组件来对它的props
进行浅比拟
const Button = React.memo((props) => {// 具体的组件});
留神:**React.memo 等效于 **
`PureComponent,它只浅比拟 props。这里也能够应用
useMemo` 优化每一个节点。
render
:这是函数组件体自身。componentDidMount
,componentDidUpdate
:useLayoutEffect
与它们两的调用阶段是一样的。然而,咱们举荐你 一开始先用 useEffect,只有当它出问题的时候再尝试应用useLayoutEffect
。useEffect
能够表白所有这些的组合。
// componentDidMount
useEffect(()=>{// 须要在 componentDidMount 执行的内容}, [])
useEffect(() => {
// 在 componentDidMount,以及 count 更改时 componentDidUpdate 执行的内容
document.title = `You clicked ${count} times`;
return () => {
// 须要在 count 更改时 componentDidUpdate(先于 document.title = ... 执行,恪守先清理后更新)// 以及 componentWillUnmount 执行的内容
} // 当函数中 Cleanup 函数会依照在代码中定义的程序先后执行,与函数自身的个性无关
}, [count]); // 仅在 count 更改时更新
请记得 React 会期待浏览器实现画面渲染之后才会提早调用,因而会使得额定操作很不便
componentWillUnmount
:相当于useEffect
外面返回的cleanup
函数
// componentDidMount/componentWillUnmount
useEffect(()=>{
// 须要在 componentDidMount 执行的内容
return function cleanup() {// 须要在 componentWillUnmount 执行的内容}
}, [])
componentDidCatch
andgetDerivedStateFromError
:目前 还没有 这些办法的 Hook 等价写法,但很快会加上。
class 组件 | Hooks 组件 |
---|---|
constructor | useState |
getDerivedStateFromProps | useState 外面 update 函数 |
shouldComponentUpdate | useMemo |
render | 函数自身 |
componentDidMount | useEffect |
componentDidUpdate | useEffect |
componentWillUnmount | useEffect 外面返回的函数 |
componentDidCatch | 无 |
getDerivedStateFromError | 无 |