一、props
1、什么是props?
定义:props是父子组件之间通信的纽带,它表示的是父组件传递过来的自定义属性和children。
props是一个对象,通过props可以访问到父组件传递过来的自定义属性和children。
props是只读的(不能修改它)
通过props,可以向子组件传递ReactNode(ReactElement、基本数据类型、数组、JSX对象)、普通对象、函数等。
2、React的两种组件
什么组件?组件就是一个段可以被复用的代码块。组件化的意义:封装、快速开发。
进一步理解组件:组件实际就是一个“函数”,它接收props作为入参,返回用于视图渲染的JSX元素。因为props是只读的,所以每一个React组件都是一个“纯函数”。(什么是纯函数?入参不能改,给相同入参时总是返回唯一的结果。)
提示:虽然props不能修改,但可以参与业务逻辑运算。
(一)函数式组件(无状态组件)
const Hello = (props) => (<div></div>)
特点:它只有props,没有state状态,也没有this、生命周期、ref、上下文等特性。所以它的性能较好。
提示:自 React v16.8 以后,我们可以通过 Hooks API 来模拟上述缺失的React特性。
(二)类组件(有状态组件)
class Hello extends React.Component { // this.props }
特点:它有props,也有state状态、this、生命周期、ref、上下文等特性。相对于函数式组件其性能较差。
提示:自 React v16.8 以后,类组件越来越少使用了。现在市场中主流是Hooks+函数式组件。
3、父子组件
什么是 props.children ?它代表是自定义组件内部所嵌套的ReactNode列表。
在自定义属性时,不要把属性名命名为 children,因为它有特殊含义。
在props.children向子组件传递children时,建议传递ReactNode列表,不要传递普通数据。如果要传递数据,建议使用自定义props。
4、父子组件通信:父传子使用自定义属性,子传父通过自定义事件。(从Vue的角度来理解父子组件)
父组件给的props如果是“普通数据”,用于渲染、用于逻辑操作。
父组件给的props如果是“函数”,在子组件中可调用并向父组件返回数据。
父组件给的props如果“JSX对象”,在子组件中直接参与渲染。
5、props是“组件化(组合)”的语法基础。
二、state
1、描述
state 是React组件自身的数据,具有“响应式”的特性(当state变量被this.setState()修改时,视图会自动更新)。
这里的“响应式”和vue响应式是完全不同的,React中没有“数据支持、依赖收集”等工作。
1、定义state
在类组件中才有state(函数式组件中没有state),必须在constructor这个生命周期中定义state。
state必须“先定义、再使用”。state变量一般是基本数据类型、数组、普通对象。不能定义其它的数据类型。
2、使用state
在类组件中,使用this.state来访问所有的state变量。
3、state的特点
当使用this.setState()这个专属方法来修改state时,这会触发render()运行并返回新的“Fiber”,进一步执行“协调运行”(找到最小化的脏节点集合),最后一次“提交”更新DOM。
state是当前组件的“自有数据”,可以通过props传递给后代组件,但不能传递给父组件。这就是React中“单向数据流”(自上而下)。
4、如何优雅地修改state变量?
修改state变量必须使用this.setState()这个专属方法。不要直接修改state,因为直接修改state不会触发render()。
每次修改state时都要考虑“新值与旧值是否有”,如果有关使用this.setState(fn,callback)这种语法,如果无关使用this.setState({},callback)这种语法。
示例:this.setState({name:'GP8'}) this.setState(state=>({count:state.count+1}))
5、this.setState()异步性
在合成事件(on*开头的系列事件、生命周期钩子)中,this.setState()是异步的。
在微任务函数体中(Promise.then)中,this.setState()是同步的。
在非合成事件中(定时器、DOM事件)中,this.setState()是同步的。
this.setState() 为什么默认是异步?
因为开发者在同一个“合成事件”中有可能多次调用this.setState(),如果它同步的,这会导致多次render(),这显然是一种性能损耗。所以,React在设计this.setState()把它变成异步的,这样的好处是更好保证this.setState()的浅合并,以节省性能。
什么是合成事件?
on*事件处理器、生命周期钩子都是合成事件。合成事件是React官方专门封装一组API,这些合成事件是“可控的”,React就可以放心地把合成事件中的this.setState()变成异步的,目的是为了对多个this.setState()进行浅合并,以节省性能。
this.setState()在合成事件中是异步的。那我们如何知道这个异步任务完成了?
方案一:this.setState(fn|{}, callback) 在callback被调用时就说明这个异步工作完成了。
方案二:componentDidUpdate() 当这个生命周期被触发时,说明这个异步工作完成了。
建议:React官方建议,方案二更靠谱,方案一尽量少用。
测试this.setState()的同步性:在非合成事件中,它都是同步的。如果一定要给个解释,React无法操控“非合成事件”,所以没有办法把this.setState()变成异步的,所以也不存在性能优化。
场景一:在定时器中,this.setState()是同步的。
场景二:用dom、ref的方式绑定事件,在事件处理器中,this.setState()也是同步的。
场景三:在new Promise().then()中,this.setState()是同步的。(异议:后面再用真实ajax测试)
6、this.setState() 浅合并
理解:在同一个更新周期(合成事件)中,如果多次调用this.setState(),React会自动将它们合并,以节省性能。
三、事件
事件绑定原则:尽可能地使用合成事件来绑定事件。
合成事件:https://zh-hans.reactjs.org/docs/events.html
1、如何绑定事件?
语法一:使用ES5的方式来绑定事件 onClick={this.handle.bind(this)}
语法二:使用ES6的方式来绑定事件 onClick={()=>this.handle()}
建议:使用“语法二”来绑定事件
2、如何拿到事件对象?
ES5方式绑定事件,事件处理器的最后一个参数永远都事件对象。
ES6方式绑定事件,需要手动传递事件对象。
3、事件处理器如何自定义传递参数?
ES5方式事件传参,onClick={this.handle.bind(this, 'arg', ...)}
ES6方式事件传参,onClick={ev=>this.handle('arg', ev)}
4、如何阻止冒泡、默认事件?
用事件对象的api来实现(二阶段的知识)
ev.stopPropagation()
ev.preventDefault()
5、如何监听键盘事件?
用事件对象的api来实现(二阶段的知识) ev.keyCode
四、条件渲染
条件渲染:元素的显示与隐藏,这里巧用的都是JSX语法。
1、单一元素的条件渲染
语法:{ bol && <jsx> }
2、两个元素的条件渲染
语法: { bol ? <jsx1> : <jsx2> }
3、多个元素的条件渲染
语法:建议封装“自定义的渲染函数” function renderThing() { return <jsx> }
4、动态class
语法:className={'类名的拼接'}
5、动态style
语法:style={ css样式的键值对 }
五、列表渲染
列表渲染:React官方建议使用map()进行渲染
语法基础:JSX支持对数组的渲染。
为什么官方建议使用map()方法来实现列表渲染?
语法:const newList = list.map(fn),
特点:map()方法的特点,对源数据进行fn处理,返回一个新的jsx数组。
六、表单绑定
表单绑定:除了文件上传以外,都要使用受控表单。
1、两类表单
受控表单,表单/类表单的value或checked由state控制着。
非受控表单,表单/类表单的value或checked与state完全无关。
原则:除了文件上传以外,所有表单都必须是受控表单。
对于普通文本表单、select表单,用value属性来受控,用onChange取值。
对于radio/checkbox表单,用checked属性来受控,用onChange取值。
对于自定义的“类表单”,我们建议用自定义属性value来受控,用自定义onChange取值。
2、表单的单向绑定
意思是在React中表单的取值操作必须手动完成。
最佳实践:当页面中表单特别多时,我们只封装一个change handler来取所有表单的值。也就是对change handler方法的复用。复用的方式有很多,可以自定义dataset、自定义事件传参来实现。