目录
事件处理
- 通过onXxx属性指定事件处理函数(注意大小写)
- React使用的是自定义(合成)事件, 而不是使用的原生DOM事件 为了更好的兼容性
- React中的事件是通过事件委托方式处理的(委托给组件最外层的元素) 为了高效
- 通过event.target得到发生事件的DOM元素对象 不要过度使用ref
<script type="text/babel">
//创建类组件
class Demo extends React.Component {
render() {
return (
<div>
<input type="text" onBlur={this.showData} />
</div>
)
}
showData = (event) => {
console.log(event);
}
}
ReactDOM.render(<Demo />, document.getElementById('test'))
</script>
收集表单数据
包含表单的组件分类
- 受控组件 输入dom 维护状态(双向绑定)
<script type="text/babel">
//创建类组件
class Demo extends React.Component {
render() {
return (
<form onSubmit={this.handle}>
用户名:<input onChange={this.saveUsername} type="text" />
密码:<input onChange={this.savePassword} type="password" />
<button>登录</button>
</form>
)
}
state = {
username: '',
password: ''
}
saveUsername = (e) => {
this.setState({ username: e.target.value })
}
savePassword = (e) => {
this.setState({ password: e.target.value })
}
}
ReactDOM.render(<Demo />, document.getElementById('test'))
- 非受控组件 输入类dom 现用现取
<script type="text/babel">
//创建类组件
class Demo extends React.Component {
render() {
return (
<form onSubmit={this.handle}>
用户名:<input ref={c => this.password = c} type="text" />
密码:<input ref={c => this.username = c} type="password" />
<button>登录</button>
</form>
)
}
handle = (e) => {
e.preventDefault()//阻止默认行为
const { password, username } = this
alert(`你的用户名是${username.value},你的密码是${this.password.value}`)
}
}
ReactDOM.render(<Demo />, document.getElementById('test'))
高阶函数_函数科里化
如果一个函数符合下面两个规范中的任意一个那该函数就是高阶函数
- 若A函数,接收的参数是一个函数,那么A就可以称为高阶函数
- 若A函数调用的返回值是一个函数,那么A就可以称为高阶函数
- 常见的高阶函数有:Promise,setTimeout arr.map
函数的科里化:通过函数调用,继续返回函数的方式,实现多次接收参数后统一函数编码的形式
function num(a) {
return (b) => {
return (c) => {
return a + b + c
}
}
}
console.log(num(1)(2)(3));
科里化实现 受控组件
必须把一个函数交给onChange作为回调
<script type="text/babel">
//创建类组件
class Demo extends React.Component {
render() {
return (
<form onSubmit={this.handle}>
用户名:<input onChange={this.saveFormDate('username')} type="text" />
密码:<input onChange={this.saveFormDate('password')} type="password" />
<button>登录</button>
</form>
)
}
state = {
username: '',
password: ''
}
saveFormDate = (type) => {
return (e) => {
this.setState({ [type]: e.target.value }) //中括号读取对象的值
}
}
}
ReactDOM.render(<Demo />, document.getElementById('test'))
不使用科里化实现 受控组件
<script type="text/babel">
//创建类组件
class Demo extends React.Component {
render() {
return (
<form onSubmit={this.handle}>
用户名:<input onChange={event => this.saveFormDate('username', event)} type="text" />
密码:<input onChange={event => this.saveFormDate('password', event)} />
<button>登录</button>
</form>
)
}
state = {
username: '',
password: ''
}
saveFormDate = (type, e) => {
this.setState({ [type]: e.target.value })
}
}
ReactDOM.render(<Demo />, document.getElementById('test'))
</script>
生命周期
认识生命周期
挂载 mount 卸载 unmount
人的生命周期 生命周期回调函数 生命周期钩子函数 生命周期函数 钩子函数
- 将要出生 ===》起名字
- 出生了 ===》婴儿用品 componentDidMount(组件挂载完毕)
- 会说话了===》记录一下
- 会走路了===》记录一下
- 上小学了===》记录一下
- 病危了===》交代后事 componentWillUnmount (组件将要卸载)
<script type="text/babel">
//创建类组件
class Demo extends React.Component {
// 渲染组件
render() {
return (
<div>
<h2 style={{ opacity: this.state.opacity }}>react学不会怎么办</h2>
<button onClick={this.death}>不活了</button>
</div>
)
}
//初始化状态
state = { opacity: 1 }
//组件挂载完毕
componentDidMount() {
this.timer = setInterval(() => {
console.log(111);
let { opacity } = this.state
opacity -= 0.1
if (opacity <= 0) opacity = 1
this.setState({ opacity })
}, 200);
}
//组件将要卸载
componentWillUnmount() {
clearInterval(this.timer)
}
death = () => {
//卸载组件
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
}
ReactDOM.render(<Demo />, document.getElementById('test'))
</script>
生命周期流程图(旧)
- 组件从创建到死亡它会经历一些特定的阶段。
- React组件中包含一系列勾子函数(生命周期回调函数), 会在特定的时刻调用。
- 我们在定义组件时,会在特定的生命周期回调函数中,做特定的工作。
生命周期的三个阶段(旧)
1. 初始化阶段: 由ReactDOM.render()触发---初次渲染
- constructor()
- componentWillMount()
- render()
- componentDidMount()----常用:做初始化的事 例如开启定时器 发送请求 订阅消息
2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发
- shouldComponentUpdate()
- componentWillUpdate()
- render()---->必须
- componentDidUpdate()
强制更新
- componentWillUpdate()
- render()
- componentDidUpdate()
子组件更新过程
- componentWillReceiveProps
- componentWillUpdate()
- render()
- componentDidUpdate()
3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
- componentWillUnmount()==>做一些收尾的事 关闭定时器 取消定时器
<script type="text/babel">
//创建类组件
class Demo extends React.Component {
//构造器
constructor(props) {
super(props)
console.log('constructor---构造器');
// 初始化状态
this.state = { count: 0 }
}
render() {
console.log('render---渲染');
const { count } = this.state
return (
<div>
<h2>当前求和为{this.state.count}</h2>
<button onClick={this.add}>点我加1</button>
<button onClick={this.death}>卸载组件</button>
<button onClick={this.force}>强制更新</button>
</div>
)
}
//组件将要挂载的构子
componentWillMount() {
console.log('componentWillMount---将要挂载');
}
//组件挂载完毕
componentDidMount() {
console.log('componentDidMount---挂载完毕');
}
//组件将要卸载
componentWillUnmount() {
console.log('componentDidMount---将要卸载');
}
//控制组件更新的阀门 布尔值true继续false不更新
shouldComponentUpdate() {
console.log('shouldComponentUpdate---组件更新的阀门');
return true;
}
//组价将要更新的构子
componentWillUpdate() {
console.log('componentWillUpdate----将要更新');
}
//组件更新完毕的构子
componentDidUpdate() {
console.log('componentDidUpdate---更新完毕');
}
// 加1按钮的回调
add = () => {
//获取原状态
let { count } = this.state
//更新状态
this.setState({ count: count + 1 })
}
// 卸载组组价的按钮回调
death = () => {
//卸载组件
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
//不更新状态刷新页面的回调
force = () => {
this.forceUpdate()
}
}
class A extends React.Component {
render() {
return (
<div>
<div>我是a组件</div>
<button onClick={this.changeCar}>换车</button>
<B carName={this.state.carName} />
</div>
)
}
//初始化状态
state = { carName: '奔驰' }
changeCar = () => {
this.setState({ carName: '奥拓' })
}
}
class B extends React.Component {
render() {
console.log('render---渲染');
return (
<div>我是b组件,接收到的车是{this.props.carName}</div>
)
}
//组价将要接收新的props
componentWillReceiveProps(props) {
console.log('componentWillReceiveProps--将要接收新值', props);
}
//控制组件更新的阀门 布尔值true继续false不更新
shouldComponentUpdate() {
console.log('shouldComponentUpdate---组件更新的阀门');
return true;
}
//组价将要更新的构子
componentWillUpdate() {
console.log('componentWillUpdate----将要更新');
}
//组件更新完毕的构子
componentDidUpdate() {
console.log('componentDidUpdate---更新完毕');
}
}
//渲染组件
ReactDOM.render(<A />, document.getElementById('test'))
</script>
生命周期流程图(新)
所有Will相关的都要加 UNSAFE_ 除卸载构子
getDerivedStateFromProps(得到一个派生的状态) 静态方法 返回 null 或 状态对象 即state的值在任何时候都要取决于props
getSnapshotBeforeUpdate(更新之前获取快照) 返回nul或者一个快照值
1. 初始化阶段: 由ReactDOM.render()触发---初次渲染
- constructor()
- getDerivedStateFromProps
- render()
- componentDidMount()
2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发
- getDerivedStateFromProps
- shouldComponentUpdate()
- render()
- getSnapshotBeforeUpdate
- componentDidUpdate()
3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
- componentWillUnmount()
<script type="text/babel">
//创建类组件
class Demo extends React.Component {
//构造器
constructor(props) {
super(props)
console.log('constructor---构造器');
// 初始化状态
this.state = { count: 0 }
}
render() {
console.log('render---渲染');
const { count } = this.state
return (
<div>
<h2>当前求和为{this.state.count}</h2>
<button onClick={this.add}>点我加1</button>
<button onClick={this.death}>卸载组件</button>
<button onClick={this.force}>强制更新</button>
</div>
)
}
// 即state的值在任何时候都要取决于props
static getDerivedStateFromProps(props, state) {
console.log('getDerivedStateFromProps---获取派生状态', props, state);
return null
}
//组件挂载完毕
componentDidMount() {
console.log('componentDidMount---挂载完毕');
}
//组件将要卸载
componentWillUnmount() {
console.log('componentDidMount---将要卸载');
}
//控制组件更新的阀门 布尔值true继续false不更新
shouldComponentUpdate() {
console.log('shouldComponentUpdate---组件更新的阀门');
return true;
}
//在更新之前获取快照
getSnapshotBeforeUpdate() {
console.log('getSnapshotBeforeUpdate');
return 999
}
//组件更新完毕的构子,接收的props和state的旧值
componentDidUpdate(preProps, preState, Snapshot) {
console.log('componentDidUpdate---更新完毕', preProps, preState, Snapshot);
}
// 加1按钮的回调
add = () => {
//获取原状态
let { count } = this.state
//更新状态
this.setState({ count: count + 1 })
}
// 卸载组组价的按钮回调
death = () => {
//卸载组件
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
//不更新状态刷新页面的回调
force = () => {
this.forceUpdate()
}
}
//渲染组件
ReactDOM.render(<Demo count='199' />, document.getElementById('test'))
</script>
getSnapshotBeforeUpdate应用场景
- 在更新之前获取快照 getSnapshotBeforeUpdate
- 组件更新完毕的构子,接收的props和state的旧值,快照值 componentDidUpdate
- componentDidMount:组件挂载完毕
- scrollHeight:内容高度
- scrollTop:滚动条的位置
-
设置滚动条的位置 = 滚动条的位置+(当前内置高度-更新前的内容高度)
-
news, ...newsArr]新数组拼接旧数组
<script type="text/babel">
//创建类组件
class Demo extends React.Component {
render() {
return (
<div className="list" ref="list">
{
this.state.newsArr.map((n, index) => {
return <div key={index} className="news">{n}</div>
})
}
</div>
)
}
state = { newsArr: [] }
componentDidMount() {
setInterval(() => {
//获取原状态
const { newsArr } = this.state
//模拟一条新闻
const news = '新闻' + (newsArr.length + 1)
//更新状态
this.setState({ newsArr: [news, ...newsArr] })
}, 1000);
}
//在更新之前获取快照
getSnapshotBeforeUpdate() {
//更新前的内容高度
return this.refs.list.scrollHeight
}
//组件更新完毕的构子,接收的props和state的旧值,快照值
componentDidUpdate(preProps, preState, Snapshot) {
//设置滚动条的位置 = 滚动条的位置+(当前内置高度-更新前的内容高度)
this.refs.list.scrollTop += this.refs.list.scrollHeight - Snapshot
}
}
ReactDOM.render(<Demo />, document.getElementById('test'))
</script>
生命周期总结
重要的勾子
- render:初始化渲染或更新渲染调用
- componentDidMount:开启监听, 发送ajax请求
- componentWillUnmount:做一些收尾工作, 如: 清理定时器
即将废弃的勾子
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
现在使用会出现警告,下一个大版本需要加上UNSAFE_前缀才能使用,以后可能会被彻底废弃,不建议使用。
DOM的diff算法
概念
diff算法 逐层对比, 最小力度是标签节点
比较更新前跟更新后的虚拟dom的差异部分生成真实的dom节点
key的值的作用
1. 虚拟DOM中key的作用:
1). 简单的说: key是虚拟DOM对象的标识, 在更新显示时key起着极其重要的作用。
2). 详细的说: 当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】 随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
a. 旧虚拟DOM中找到了与新虚拟DOM相同的key:
- (1).若虚拟DOM中内容没变, 直接使用之前的真实DOM
- (2).若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM
b, 旧虚拟DOM中未找到与新虚拟DOM相同的key,根据数据创建新的真实DOM,随后渲染到到页面
2. 用index作为key可能会引发的问题:
- 若对数据进行:逆序添加、逆序删除等破坏顺序操作: 会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
- 如果结构中还包含输入类的DOM:会产生错误DOM更新 ==> 界面有问题。
- 注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的
3.开发中如何选择key?:
- 1.最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
- 2.如果确定只是简单的展示数据,用index也是可以的。
案例
慢动作回放----使用index索引值作为key 在数组前面加了一条数据 导致key值对不上产生效能问题
初始数据:
- {id:1,name:'小张',age:18},
- {id:2,name:'小李',age:19},
初始的虚拟DOM:
- <li key=0>小张---18<input type="text"/></li>
- <li key=1>小李---19<input type="text"/></li>
更新后的数据:
- {id:3,name:'小王',age:20},
- {id:1,name:'小张',age:18},
- {id:2,name:'小李',age:19},
更新数据后的虚拟DOM:
- <li key=0>小王---20<input type="text"/></li>
- <li key=1>小张---18<input type="text"/></li>
- <li key=2>小李---19<input type="text"/></li>
慢动作回放----使用id唯一标识作为key
初始数据:
- {id:1,name:'小张',age:18},
- {id:2,name:'小李',age:19},
初始的虚拟DOM:
- <li key=1>小张---18<input type="text"/></li>
- <li key=2>小李---19<input type="text"/></li>
更新后的数据:
- {id:3,name:'小王',age:20},
- {id:1,name:'小张',age:18},
- {id:2,name:'小李',age:19},
更新数据后的虚拟DOM:
- <li key=3>小王---20<input type="text"/></li>
- <li key=1>小张---18<input type="text"/></li>
- <li key=2>小李---19<input type="text"/></li>
class Person extends React.Component{
state = {
persons:[
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
]
}
add = ()=>{
const {persons} = this.state
const p = {id:persons.length+1,name:'小王',age:20}
this.setState({persons:[p,...persons]})
}
render(){
return (
<div>
<h2>展示人员信息</h2>
<button onClick={this.add}>添加一个小王</button>
<h3>使用index(索引值)作为key</h3>
<ul>
{
this.state.persons.map((personObj,index)=>{
return <li key={index}>{personObj.name}---{personObj.age}<input type="text"/></li>
})
}
</ul>
<hr/>
<hr/>
<h3>使用id(数据的唯一标识)作为key</h3>
<ul>
{
this.state.persons.map((personObj)=>{
return <li key={personObj.id}>{personObj.name}---{personObj.age}<input type="text"/></li>
})
}
</ul>
</div>
)
}
}