本笔记是根据徐超的《React进阶之路》整理的,其中少部分内容来自网络和个人理解。这本书出版时间是2018年初,当时尚未出现hooks,书中一些案例、方法放到今天也不一定是最佳实践,但书中知识体系完整,涉及到很多基础,作为入门学者也值得一读。
本人做笔记时把部分常用又简单的知识点没有做整理,大家看的时候注意。能来到这里也是本人荣幸,欢迎大家有问题留言讨论!
目录
第一篇 基础
第一章 初识React
ES6基础
- 箭头函数的this指向是定义时所在的上下文对象。
function foo(){ this.bar = 1; this.f = a => a + this.bar } // 等价于 function foo(){ this.bar = 1; this.f = (fuction (a){ return a + this.bar }).bind(this) }
- 函数参数作为数组、对象的解构赋值
// 数组参数 [[1, 2], [3, 4]].map(([a, b]) => a + b); // 对象参数 let jsonData = { id: 42, status: "OK", data: [867, 5309] }; let { id, status, data: number } = jsonData; console.log(id, status, number);
- 扩展符可以复制对象:
let obj = { ...anotherObj }
第二章 React基础
1. JSX
- 自定义标签属性只对自定义React组件生效(React16之前)
2. 组件
- 什么情况下必须写super(props),网查:
- 如果你在constructor中要使用this.props,就必须给super加参数,super(props)
- 无论有没有constructor,在render中this.props都是可以使用的,这是react自动附带的
- 如果没用到constructor,是可以不写的,react会默认添加一个空的constructor
- 有状态组件和无状态组件
开发React应用时,应先认真考虑清楚哪些组件有状态(写成class形式),哪些组件无状态(写成函数形式),应优先使用无状态组件。
原则:通过定义少数有状态组件管理整个应用的状态变化,并且将状态通过props传递给其余的无状态组件,由无状态组件完成页面的绝大部分UI渲染工作事实:项目迭代修改过程中,很多原本设定的无状态组件逐渐被改成了有状态组件,这也是react出现hooks的原因之一。
- 案例
一个帖子列表,每个帖子可以点赞。设计结构为列表是有状态组件,管理整个列表数据获取点赞行为;每个帖子是无状态组件,只负责UI展示,点赞动作传递到父组件去处理。
- 小技巧:通过传来的id修改数组中某个元素内容
const posts = this.state.posts.map(item=>{ const newItem = item.id === id ? {...item, vote:++item.vote} : item; return newItem; })
-
如果组件里使用了定时器,则在卸载前应该清除以释放内存。
3. 生命周期
- componentWillMount可以用constructor代替
- 正常情况下,父组件的渲染必然会引起子组件的渲染
4. 列表和keys
- 不推荐使用索引index作为key,因为数据重排时元素索引会变化,不利于性能优化
5. 事件处理
- js原生事件的默认行为可通过函数中 return false来阻止;而react中要显式调用事件对象的preventDefault方法来阻止默认行为。
- 要使用js原生事件,可通过事件对象中的nativeEvent属性获取。
- 为什么事件的回调方法要绑定this?因为事件名称(如onClick)是一个中间变量,回调方法是组件内的一个方法。在render时,事件会把回调方法传递给真实的事件名称(如onclick),相当于一个方法被单独传出去了,而丢了当前的this,所以要显示绑定。举例代码如下:
let obj = { // 相当于定义组件 tmp:'Yes!', // 相当于state中的数据 onClick:function(){ // 事件的回调 console.log(this.tmp); } }; obj.onClick(); // 此时为obj调用handClick函数,其中的this指向obj,所以结果为Yes let onclick = obj.onClick; onclick(); //直接调用,其中this指向window,但window没有定义tmp,所以结果为undefined
- 箭头函数定义时的this就会被存入函数内。事件处理的3中方法:
- 在元素中直接用箭头函数定义。缺点:每次render都要创建事件函数,带来额外性能开销,但多数情况下这点损失是可以忽略的。
<button onClick = {(event)=>{console.log(this.state.oneState)}}>click</button>
如果函数体比较复杂就显得不优雅,可以封起来写到组件的方法中
handleClick(event){ // do something } …… <button onClick = {(event)=>{this.handleClick(event))}>click</button>
- 将组件的方法赋值给事件属性,但这样需要在构造函数中用bind绑定this到当前对象。缺点:增加构造函数的不优雅;优点:没有额外性能开销
constructor{ this.handleClick = this.handleClick.bind(this) } handleClick(event){ // do something } …… <button onClick = {this.handleClick}>click</button>
也可以把this绑定写到事件赋值时,而且这种写法可以传参数。这样做缺点也是增加性能开销。
handleClick(param, event){ // do something } …… <button onClick = {this.handleClick.bind(this, param))}>click</button>
- 用属性初始化方法。缺点:因为是ES2017里面的方法,所以要确认环境支持;不能传参。不过当你看到这篇文章时,基本不用担心环境问题了
handleClick = (event) => { // do something } …… <button onClick = {this.handleClick)}>click</button>
- 如果需要传参,可以结合b与c,即不用bind的传参写法
handleClick = (param) => { // do something } …… <button onClick = {()=>this.handleClick(param)))}>click</button>
- 在元素中直接用箭头函数定义。缺点:每次render都要创建事件函数,带来额外性能开销,但多数情况下这点损失是可以忽略的。
6. 表单
- input这类自身有状态的组件默认是非受控组件,但如果把状态放到react中进管理,则是受控组件。用react管理的原理是:使用value设置表单的值,使用onChange属性监听值的变化
- 技巧:处理多个表单元素时,可以为各个元素分别定制name属性,而使用同一个监听函数处理变化,根据每个元素的name属性区分事件来源。这样比每个元素一个处理函数优雅多了。
- 下拉选框的选中状态在原生中是放在每个option的属性中,而在react中放在select的属性上。
- 案例:标题可编辑的帖子(略)
- 也可以使用非受控组件使用表单,即通过ref将表单元素指向到组件内的变量,通过获取value值来取值。如果要设置初始值,就要用defaultValue。但这种使用方法违背了受控的思想原则,应尽量少用。
第三章 react16新特性
1. 组件返回类型
之前类组件的render方法只能返回单个的React元素,现在还可以返回字符串或由React元素组成的数组。
2. 错误边界
定义componentDidCatch(error,info)方法,此组件的子组件抛出的错误会在这里被处理。
注意:使用react-create-app创建的项目,当程序发生错误时,页面上会被创建一个浮层显示错误信息,要观察抓错误的组件的显示情况,需要先关闭浮层。
3. 传送门portal
ReactDOM.createPortal(child, container)
第一个参数child类型和组件render方法的返回类型一致(参见新特性第一条),第二个参数是要挂载的DOM节点。弹出的模态框适用于这种方式实现,可直接挂载到body下。
4. DOM接受的参数
之前dom元素会把不识别的参数丢弃,现在会接收。
第二篇 进阶
第四章 深入组件
1. 组件state
- state应该代表一个组件UI呈现的完整最小状态集,大概分为组件渲染展现的数据和组件呈现的判断依据两类。无须作为state的情况有:
- 来自于父组件的props
- 在组件整个生命周期中保持不变的值
- 可以通过其他state或props计算得到的值
- 在render中没有用到的值
- 修改state
- 不能直接修改
this.state.counter = 123 // 错误 this.setState({ // ++操作符不能生效 counter: this.state.counter++ })
- state的更新是异步的,出于性能原因,react会把多次修改操作合并成一次来执行。比如,点击一次按钮连续执行两次加1的setState操作,最终state只加1。所以不能依赖当前的state计算下一次state,props同理。正确的做法是setState里面接受一个函数来处理,这样可以连续多次执行:
this.setState((preState,props)=>({ counter: preState.counter+1 }))
- 不能直接修改
- state作为不可变状态,修改情况分3类:
- 值为字符串、数字、null、undefined、布尔值等,直接赋新值;
- 值为数组,可通过如下方法实现。注意不要使用push、pop、shift、unshift、splice等修改原数组本身的方法。
// 使用数组的concat方法 this.setState(preState=>({ books:preState.books.concat(['React Guide']) })) // ES6 扩展符 this.setState(preState=>({ books:[...state.books, 'React Guide'] })) // 截取部分元素作为新状态 this.setState(preState=>({ books:state.books.slice(1,3)) })) // 过滤出部分元素作为新状态 this.setState(preState=>({ books:state.books.filter(item => item !== 'React') }))
- 值为普通对象
// 使用Object的assign方法 this.setState(preState=>({ owner:Object.assign({},preState.owner,{name:'Daoke'}) })) // 使用扩展符 this.setState(preState=>({ owner:{...preState.owener, ...{name:'Daoke'}} }))
- state作为不可变对象,优点有2个:
- 对状态的修改都是用新的状态覆盖,避免了修改原状态本身导致的错误,方便程序调试管理;
- 组件的shouldComponentUpdate方法中,仅需要比对前后两次状态的引用即可判断状态是否真的改变,避免了不必要的render调用。
- react(含hooks)中同步获取state值的一些方法: https://blog.csdn.net/daoke_li/article/details/112746505
2. 组件与服务器通信
- 组件挂载阶段从服务器获取数据的最佳生命周期是componentDidMount。
- 组件更新阶段从服务器获取数据的最佳生命周期是componentWillReceiveProps,但这个方法在React17中将被废弃,可以用getDerivedFromProps代替。可以判断一下特定的属性是否改变再进行数据请求。
componentWillReceiveProps(nextProps){ if(nextProps.category !== this.props.category){ // 进行数据请求 } } // 这个例子来源于网络 https://zhuanlan.zhihu.com/p/89494013 static getDerivedStateFromProps(nextProps,prevState){ //该方法是静态方法,内部禁止访问this if(nextProps.email !== prevState.email){ //通过对比nextProps和prevState,返回一个用于更新状态的对象,几相当于setState return { value:nextProps.email } } //不需要更新状态,返回null return null }
3. 组件通信
- 父子传参,略;
- 兄弟组件传参,兄弟可以不在同一个层级,但必须有共同的父元素。传参通过提升状态实现,即把需要共享的状态保存到离他们最近的父组件内,任意一个兄弟组件都可以通过父组件传来的回调函数来修改共享状态,父组件中共享状态的变化也会通过props下传到每个兄弟组件。例如,列表页和详情页同一页显示时,把当前的选中条目保存在他两的共同父组件中。
- Context,使用方式和目前React较新的版本已经有很大变化,故不再记录。
4. ref
- ref的回调函数在挂载和卸载时被调用,挂载时参数是当前DOM元素,卸载时参数为null
- 使用场景
- 在DOM元素上使用,如组件加载后让input元素自动获取焦点
componentDidMount(){ this.textInput.focus() } …… <input ref={input => this.textInput = input})>
- 在组件上使用ref,可以在组件外部操作组件内的方法
// 子组件children blur(){ this.textInput.blur() } …… <input ref={input => this.textInput = input})> // 父组件 componentDidMount(){ this.inputInstance.blur() } …… <children ref={input=>this.inputInstance = input}}>
- 父组件访问子组件的Dom节点,比如要获取这个dom的高度等属性,可以在Dom的ref中传递一个父组件中的方法作为回调
// 子组件children <input ref={props.inutRef}> // 父组件,这里的inputElement 就是子组件中的input元素 <children inputRef={el => this.inputElement = el}/>
- 1
- 在DOM元素上使用,如组件加载后让input元素自动获取焦点
<整理中……>