事件处理
react事件和DOM事件
react事件 | 原生事件 |
---|---|
onClick | onclick |
onClick={eventListener} | οnclick="eventListener()" |
e.preventDefalut | οnclick="javascript" |
class ListItem extends Component {
constructor(props){
super(props) //子类中调用父类构造函数
}
doSomethind(){
}
handleClick(e){
console.log(e)
this.doSomethind() //报错,会说找不到这个方法
}
render(){
return (
<div className='listItem'>
<span onclick={this.handleClick}>header<span>
<div>
)
}
}
在面向对象的编程中,this的使用方法会随着引用对象的差别而不同,当被对象引用时指向的是对象,单独函数引用指向的是window,严格模式是undefined
,那我们在react中怎么解决这个问题呢?
this关键词的处理
- 在jsx中使用bind方法
- 在构造函数中使用bind方法
- 使用箭头函数
class ListItem extends Component {
constructor(props){
super(props) //子类中调用父类构造函数
// 方法2
this.handleClick=this.handleClick.bind(this)
}
doSomethind(){
}
// 方法3,推荐使用,箭头函数的this总是指向定义时的对象,而不是运行时
handleClick=(e)=>{
console.log(e)
this.doSomethind() //报错,会说找不到这个方法
}
render(){
return (
<div className='listItem'>
{/*方法1*/}
<span onclick={this.handleClick.bind(this)}>header<span>
<div>
)
}
}
向事件处理程序传递参数
class ListItem extends Component {
constructor(props){
super(props) //子类中调用父类构造函数)
this.state={
conunt:1
}
}
handleClick(id){
console.log(id)
}
render(){
return (
<div className='listItem'>
{/* 方法1*/}
<span onclick={this.handleClick.bind(this,1)}>header<span>
{/* 方法2*/}
<span onclick={()=>this.handleClick(1)}>header<span>
{/* 传递事件对象event*/}
<span onclick={(e)=>this.handleClick(1,e)}>header<span>
<div>
)
}
}
向父组件传递参数
// 父组件
class App extends Component {
handDelete(id){
console.log(id)
}
render(){
<ListItem onDelete={this.handDelete}/>
}
}
// 子组件
class ListItem extends Component {
constructor(props){
super(props)
this.state={
conunt:1
}
}
render(){
return (
<div className='listItem'>
<span onclick={()=>this.props.onDelete(this.state.conunt)}>header<span>
<div>
)
}
}
React事件机制
DOM事件
在js的事件触发经过3个阶段,事件的捕获->目标对象事件的处理->事件冒泡,假设在text
中触发了事件,会经过一个捕获的阶段,父级元素将事件一直传递到本身发生的元素上,在经过本身的事件处理之后,会经历冒泡饿阶段,事件从子元素向父元素冒泡;就因为这样,事件委托成为了可能,就是将子元素的事件处理委托给父元素。
react事件
react会将所有的事件都绑定到document上,而不是某个元素上面,统一的使用事件监听,并在冒泡阶段处理事件,当挂载和卸载组件的时候 只需在统一的事件监听位置,增加或删除对象,因此极大的提高效率;当事件触发的时候,我们的组件会生成一个合成事件,然后传递到documennt
中,doucument会通过DispatchEvent
回调函数依次执行DispatchEvent
中同类型的事件监听函数,事件注册是在组件生成的时候,我们将vDom中所有的事件的原生事件documennt
中的一个监听器当中,也就是所有的事件处理函数都存在ListenerBank
中 并以key
作为索引,这样的好处是将可能要触发的事件分门别类。
react事件要素
- react事件是合成事件,不是DOM原生事件
- 在document监听所有支持的事件
- 使用统一的分发函数dispatchEvent
认识State
安装react-devtools
调试工具
如何定义State
class ListItem extends Component {
constructor(props){
super(props)
this.state={ //定义一个state
conunt:1
}
}
render(){
return (
<div className='listItem'>
<span>{this.state.count}<span>
<div>
)
}
}
修改State
- 使用setState
- setState是异步的
- State的更新是一个浅合并(shalllow merge)的过程
class ListItem extends Component {
constructor(props){
super(props)
this.state={
conunt:1
}
}
addCunt(){
console.log('step1',this.state.count)
this.setState({ //修改一个state
count:this.state.count+1
})
console.log('step2',this.state.count)
}
render(){
return (
<div className='listItem'>
<span onclick={()=>this.addCount()}>{this.state.count}<span>
<div>
)
}
}
addCount函数中打印的2次conut
的值是一样的,说明setState的处理过程是异步的而不是同步的,react在执行setState时候,优化执行的时机,多个setState合并在一起去执行,如果非要在state变化话后做一些操作的话,其实setState会接收第二个参数。
class ListItem extends Component {
constructor(props){
super(props)
this.state={
conunt:1
}
}
addCunt(){
this.setState({ //修改一个state
count:this.state.count+1
},()=>{
console.log('step3',this.state.count) // 可以拿到修改后的值
})
}
render(){
return (
<div className='listItem'>
<span onclick={()=>this.addCount()}>{this.state.count}<span>
<div>
)
}
}
创建新的状态
修改state我们都要遵循状态都应该是不可变数据
什么是不可变数据
不可变数据是函数式编程的重要概念,就是我们对已经初始化的数据不进行更改,每次更改都是创建新的对象来承载新的数据状态
状态类型
- 值类型:string、number、boolean、null、undefined
- 数组类型
- 对象
// 1.状态的类型是值类型
this.setState({
count:1,
name:'zs',
show:true
})
// 2.状态的类型是数组
const _books=this.state.books.concat('new book')
// 或
const _books=[...this.state.books,'new book')]
this.setState({
books:_books
})
// 2.状态的类型是对象
const _item=Object.assign({},this.state.item,{id:1,ame:'zs'})
// 或
const _item={...this.state.item,id:1,ame:'zs'}
this.setState({
item:_item
})
上面创建新的对象都是浅拷贝,如果要深拷贝可以用immutable.js类库 来处理不可变数据。
State进阶
通过条件判断优化渲染
下面代码是我们渲染一个列表,当我们删除第一行列表的时候,其它2个列表其实没有任何变化,但是它们的render方法还是执行了,执行了render方法其实也要diff对比,当一个大型的项目,这显然也是很耗时的,所以我们要优化它,优化有2种方法:
-
sholudComponentUpdate(利用这个钩子阻止,如果子组件没有变化就不进行渲染)
-
PureComponent(利用react提供的组件)
// 父组件
import React,{Component} from 'react';
import ListItem from './listItem'
class Stated extends Component {
state = {
listData: [
{
id: 1,
name: "Sony 65寸高清液晶电视",
price: 7000,
stock: 1
},
{
id: 2,
name: "华为 Meta30",
price: 6000,
stock: 2
},
{
id: 3,
name: "华硕画家国度笔记本电脑",
price: 10000,
stock: 3
}
]
}
renderList(){
return this.state.listData.map(item=>{
return <ListItem key={item.id} data={item} onDelete={this.handDelete}></ListItem>
})
}
handDelete=(id)=>{
const listData=this.state.listData.filter(item=>item.id!==id)
this.setState({
listData
})
}
render() {
return (
<div>
{this.state.listData.length<=0 && <div>购物车为空</div>}
{this.renderList()}
</div>
);
}
}
export default Stated;
// 子组件
import React, { Component } from 'react';
class ListItem extends Component {
state = { }
render() {
console.log('item render--虚拟dom')
return (
<div>
<span>{this.props.data.name}</span>
<button onClick={()=>{
this.props.onDelete(this.props.data.id)
}}>删除</button>
</div>
);
}
}
export default ListItem;
sholudComponentUpdate
// 子组件
import React, { Component } from 'react';
class ListItem extends Component {
state = { }
shouldComponentUpdate(nextProps,nextState){
if(nextProps.data.id===this.props.data.id) return false
return true
}
}
export default ListItem;
PureComponent
// 父组件
import React,{PrueComponent} from 'react';
import ListItem from './listItem'
class Stated extends PrueComponent {
......
}
单一数据源
尊崇单一数据源,相同的子组件的数据应该由父组件通过props
传递子组件,由父组件统一管理,避免造成数据的混乱,使用这个原则的时候,当父组件任何一个 状态的改变,会自动更新子组件这一部分,从上而下传达到子组件的,是同步的方法,子组件想操作数据,由父组件提供更新函数。
状态提升
当我们的子组件都要控制同一个数据源的时候,我们需要将数据提升到它们共同的父组件当中,然后父组件通过props传递给子组件,并由父组件进行统一管理和存储。
为什么使用不可变数据
- 可回溯
不直接在数据上修改,方便我们追溯以前的历史记录。
- 跟踪数据改变
直接修改数据,跟踪数据的改变需要把当前数据和以前数据的版本进行对比,这样整个对象树都要遍历一次 如果使用不可变数据,创建新的对象,这样我们发现是一个新的对象,那我们不需要对象树对比就知道数据发生了变化,因为对象不是同一个引用了
- 确定在react中何时重新渲染
不可变性最主要优势在于它可以帮助我们在react中创建Pure components,我们可以轻松确定不可变数据是否发生了改变,从而确实何时对组件进行重新渲染
有状态组件和无状态组件
Stateful
- 类组件
- 有状态组件
- 容器组件
Stateless
- 函数组件
- 无状态组件
- 展示组件
无状态组件,所有数据源都来自于父组件,它只做展示的作用
尽可能通过状态提升原则,将需要的状态提取到父组件中,而其他的组件使用无状态组件编写
生命周期
三个阶段的生命周期函数
三个阶段生命周期
创建阶段->更新阶段->卸载阶段
创建阶段
创建阶段constructor
-
初始化内部状态,显性设置和隐性设置
一个类必须有
constructor
方法,如果这个方法没有显示定义,一个默认的constructor
方法会被添加 -
需要使用super()调用基类的构造函数
将父类的props注入给子组件
-
可以直接修改state
创建阶段componentWillMount(16.3移除)
- UI渲染完成前调用
- 只执行一次
- 这里调用setState不会触发render
更多的时候我们把组件里面的内容,会提前到constructor
中,所以这个生命周期函数在项目中我们很少使用
创建阶段render
- 一个组件必须有的方法(我们的类组件)
- 返回一个顶级的react元素 只能有一个根元素,不能返回并列元素
- 渲染的是Dom Tree的一个React对象
创建阶段componentDidMount
- UI渲染完成后调用
- 只执行一次
- 获取一些外部数据资源
需要注意的是当父组件执行render的时候,当所有子组件都完成了创建,那么父组件才能最终的完成渲染,然后父组件执行componentDidMount
更新阶段
当state和props发生变化时,进入更新阶段
更新阶段componentWillReceiveProps(16.3移除)
只接收props
- 组件接收到新props的时候触发
- 在此对比新props和原来的props
- 不推荐使用
更新阶段shouldComponentUpdate
接收state和props
- 是否要继续执行render方法
- 可以由PureComponent自动实现
更新阶段componentDidUpdate
- 每次UI更新时调用
- 更新一些外部数据资源
卸载阶段 componentWillUnmount
- 组件移除时调用
- 可以用来做资源的释放
图解生命周期
旧的生命周期
新的生命周期
16.3以后移除了 componentWillmount、componentWillReceiveProps、componentWillUpdate
React组件设计模式
高阶组件
在业务中可能碰到许多要复用业务逻辑的情况,我们为了避免每个组件都写一段相同逻辑的代码,我们就用高阶组件。
高阶组件是对已有组件的封装,形成新的组件后,有自己的状态和逻辑;并可以传递已有的组件
高阶组件就是接收组件作为参数,并返回新的组件
const NewComponent=higherOrderComponent(OldComponent)
案例:假如多个组件需要鼠标滑入时给个提示,滑出时清空提示,那么可以把这个公共提示抽离出来复用
目录
src/
components/
Item/
index.jsx
withTooltip.js
封装Hoc组件(withTooltip.js)
import React from 'react';
const withTooltip=(Component)=>{
class Hoc extends React.Component {
state={
showToolTip:false,
content:''
}
handleOver=(e)=>{
this.setState({showToolTip:true,content:e.target.innerText})
}
handleOut=()=>{
this.setState({showToolTip:true,content:''})
}
render(){
return(
<div onMouseOver={this.handleOver} onMouseOut={this.handleOut}>
<Component action={this.state} {...props}></Component>
</div>
)
}
}
return Hoc
}
export default withTooltip
在需要的组件引入并调用(index.jsx)
import React from 'react';
import withTooltip from './withTooltip'
const ItemA = (props) => {
return (
<div className="container">
<button>Tooltip</button>
{
props.action.showToolTip && <div>{props.action.content}</div>
}
</div>
);
}
export default withTooltip(ItemA)
高阶组件特性
- 一个函数,传入一个组件,返回一个新组件
- 一般不会有ui展现
- 提供一些可复用的功能
函数作为子组件(renderProps)
解决复用业务逻辑的问题,是指一种在组件之间使用一个值为函数的props,来共享代码的的设计模式
结构
// 1.定义子组件
render(){
return (
<div>
{this.props.render(this.state)}
</div>
)
}
// 2.使用函数作为Props
<RenderPropComponent render={
(state)=>{
<div>
content
</div>
}
}/>
改写上面的案例
目录
src/
components/
Rp/
index.jsx
withTooltip.js
定义子组件(withTooltip.js)
import React from 'react';
class WithTooltip extends React.Component {
state={
showToolTip:false,
content:''
}
handleOver=(e)=>{
this.setState({showToolTip:true,content:e.target.innerText})
}
handleOut=()=>{
this.setState({showToolTip:true,content:''})
}
render(){
return(
<div onMouseOver={this.handleOver} onMouseOut={this.handleOut}>
{this.props.render(this.state)}
</div>
)
}
}
export default WithTooltip
定义父组件(index.jsx)
import React from 'react';
import WithTooltip from './withTooltip'
const ItemA = (props) => {
return (
<div className="container">
<WithTooltip
render={({showToolTip,content})=>(
<div>
<button>Tooltip</button>
{
showToolTip && <div>{content}</div>
}
</div>
)}>
</WithTooltip>
</div>
);
}
export default ItemA
函数作为子组件(Children)
改写成函数作为子组件(更加直观)推荐
定义子组件
import React from 'react';
class WithTooltip extends React.Component {
state={
showToolTip:false,
content:''
}
handleOver=(e)=>{
this.setState({showToolTip:true,content:e.target.innerText})
}
handleOut=()=>{
this.setState({showToolTip:true,content:''})
}
render(){
return(
<div onMouseOver={this.handleOver} onMouseOut={this.handleOut}>
{this.props.children(this.state)}
</div>
)
}
}
export default WithTooltip
定义父组件
import React from 'react';
import WithTooltip from './withTooltip'
const ItemB = (props) => {
return (
<div className="container">
<WithTooltip>
{({showToolTip,content})=>(
<div>
<button>Tooltip</button>
{
showToolTip && <div>{content}</div>
}
</div>
)}
</WithTooltip>
</div>
);
}
export default ItemB