学习视频:尚硅谷React技术全家桶全套完整版(零基础入门到精通/男神天禹老师亲授)_哔哩哔哩_bilibili
一、受控组件与非受控组件(收集表单信息)
以下都用一个案例来说明,效果如下:
1、受控组件
主要步骤:
(1)定义一个表单,创建 ref 容器。
(2)给相应的结点元素增加 ref 标识。
(3)通过 onSubmit 事件执行 printData 函数。
(4)从 this 中获取 username 与 password 结点元素,通过 username.value 获取结点的值。
<script type="text/babel">
class Login extends React.Component {
printData = (event) => {
event.preventDefault(); // 阻止默认事件,这里是阻止表单提交
const { username, password } = this;
alert(`用户名:${username.value},密码:${password.value}`);
}
render() {
return (
// 注意:如果没有给 input 增加 name 属性,则不会接收到具体的数据
<form action="" onSubmit={this.printData}>
用户名:<input type="text" name='username' ref={(c) => { this.username = c }} /> <br />
密码:<input type="password" name='password' ref={(c) => { this.password = c }} /> <br />
<button>登录</button>
</form>
)
}
}
ReactDOM.render(<Login />, document.querySelector('.box'))
</script>
2、非受控组件
主要步骤:
(1)定义表单,在需要获取值的表单上增加 onChange 事件。
(2)通过事件获取结点元素的值,并将值存储到 state 中去。
(3)若表单事件需要获取结点值,则从 state 中直接读取。
个人理解:受控组件是获取结点,从结点中获取值。非受控组件是直接从 state 中获取到值。
<script type="text/babel">
class Login extends React.Component {
// 初始化状态
state = {
username: '',
password: ''
}
printData = (event) => {
event.preventDefault(); // 阻止默认事件,这里是阻止表单提交
// 从状态中直接读取出所需要的值
alert(`用户名:${this.state.username},密码:${this.state.password}`);
}
usernameData = (event) => {
// 将事件的值放到状态当中去
this.setState({ username: event.target.value })
}
passwordData = (event) => {
this.setState({ password: event.target.value })
}
render() {
// 当坐在结点元素的值发生改变时,会触发 onchange 事件。
return (
<form action="" onSubmit={this.printData}>
用户名:<input type="text" name='username' onChange={this.usernameData} /> <br />
密码:<input type="password" name='password' onChange={this.passwordData} /> <br />
<button>登录</button>
</form>
)
}
}
ReactDOM.render(<Login />, document.querySelector('.box'))
</script>
二、函数柯里化
高阶函数:如果一个函数符合下面两个规范中的任何一个,那该函数就是高阶函数。
(1)接收的参数是一个函数 。
(2)调用的返回值依然是一个函数。
@ 常见的高阶函数有:Promise、setTimeout、arr.map()等等。
函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
<script type="text/javascript" > /* function sum(a,b,c){ return a+b+c } */ function sum(a){ return(b)=>{ return (c)=>{ return a+b+c } } } const result = sum(1)(2)(3) console.log(result); </script>
函数柯里化实现:
<script type="text/babel">
class Login extends React.Component {
// 初始化状态
state = {
username: '',
password: ''
}
printData = (event) => {
event.preventDefault(); // 阻止默认事件,这里是阻止表单提交
// 从状态中直接读取出所需要的值
alert(`用户名:${this.state.username},密码:${this.state.password}`);
}
saveData = (dataType) => {
return (event) => {
this.setState({ [dataType]: event.target.value })
}
}
render() {
return (
<form action="" onSubmit={this.printData}>
用户名:<input type="text" name='username' onChange={this.saveData('username')} /> <br />
密码:<input type="password" name='password' onChange={this.saveData('password')} /> <br />
<button>登录</button>
</form>
)
}
}
ReactDOM.render(<Login />, document.querySelector('.box'))
</script>
不用函数柯里化的实现:
<script type="text/babel">
class Login extends React.Component {
// 初始化状态
state = {
username: '',
password: ''
}
printData = (event) => {
event.preventDefault(); // 阻止默认事件,这里是阻止表单提交
// 从状态中直接读取出所需要的值
alert(`用户名:${this.state.username},密码:${this.state.password}`);
}
saveData = (dataType, event) => {
this.setState({ [dataType]: event.target.value })
}
render() {
return (
<form action="" onSubmit={this.printData}>
用户名:<input type="text" name='username' onChange={(event) => { this.saveData('username', event) }} /> <br />
密码:<input type="password" name='password' onChange={(event) => { this.saveData('password', event) }} /> <br />
<button>登录</button>
</form>
)
}
}
ReactDOM.render(<Login />, document.querySelector('.box'))
</script>
三、生命周期
1、旧生命周期
1.1 概述
【 PS:挂载:mount;卸载:unmount 】
(1)初始化阶段:由ReactDOM.render()触发 —— 初次渲染
① constructor() —— 构造器
② componentWillMount() —— 组件将要挂载
③ render()
④ componentDidMount() —— 组件挂载完毕【常用,做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息】
(2)更新阶段:由组件内部 this.setSate() 或父组件 render 触发
① shouldComponentUpdate() —— 控制组件更新的“阀门”
② componentWillUpdate() —— 组件将要更新
③ render() 【必须使用】
④ componentDidUpdate() —— 组件更新完毕
(3)卸载组件:由 ReactDOM.unmountComponentAtNode() 触发
① componentWillUnmount() —— 组件将要卸载【常用,做一些收尾的事,例如:关闭定时器、取消订阅消息】
1.2 案例演示
效果:求和案例,每次点击+1按钮,count+1并显示在页面上。
以下这段代码直接 copy 学习视频中的:
<script type="text/babel">
//创建组件
class Count extends React.Component{
// 构造器
constructor(props){
console.log('Count---constructor');
super(props)
//初始化状态
this.state = {count:0}
}
// 加 1 按钮的回调
add = ()=>{
// 获取原状态
const {count} = this.state
// 更新状态
this.setState({count:count+1})
}
// 卸载组件按钮的回调
death = ()=>{
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
// 强制更新按钮的回调
force = ()=>{
this.forceUpdate()
}
// 组件将要挂载的钩子
componentWillMount(){
console.log('Count---componentWillMount');
}
// 组件挂载完毕的钩子
componentDidMount(){
console.log('Count---componentDidMount');
}
// 组件将要卸载的钩子
componentWillUnmount(){
console.log('Count---componentWillUnmount');
}
// 控制组件更新的“阀门”
shouldComponentUpdate(){
console.log('Count---shouldComponentUpdate');
return true
}
// 组件将要更新的钩子
componentWillUpdate(){
console.log('Count---componentWillUpdate');
}
// 组件更新完毕的钩子
componentDidUpdate(){
console.log('Count---componentDidUpdate');
}
render(){
console.log('Count---render');
const {count} = this.state
return(
<div>
<h2>当前求和为:{count}</h2>
<button onClick={this.add}>点我+1</button>
<button onClick={this.death}>卸载组件</button>
<button onClick={this.force}>不更改任何状态中的数据,强制更新一下</button>
</div>
)
}
}
// 父组件A
class A extends React.Component{
// 初始化状态
state = {carName:'奔驰'}
changeCar = ()=>{
this.setState({carName:'奥拓'})
}
render(){
return(
<div>
<div>我是A组件</div>
<button onClick={this.changeCar}>换车</button>
<B carName={this.state.carName}/>
</div>
)
}
}
// 子组件B
class B extends React.Component{
// 组件将要接收新的props的钩子
componentWillReceiveProps(props){
console.log('B---componentWillReceiveProps',props);
}
render(){
console.log('B---render');
return(
<div>我是B组件,接收到的车是:{this.props.carName}</div>
)
}
}
// 渲染组件
ReactDOM.render(<Count/>,document.getElementById('test'))
</script>
2、新生命周期
2.1 概念
(1)初始化阶段:由ReactDOM.render()触发 —— 初次渲染
① constructor()
② getDerivedStateFromProps —— 【新增:从 props 中获取衍生的 state】
③ render()
④ componentDidMount()
(2)更新阶段: 由组件内部 this.setSate() 或父组件重新 render 触发
① getDerivedStateFromProps
② shouldComponentUpdate()
③ render()
④ getSnapshotBeforeUpdate —— 【新增:在更新前获取快照】
⑤ componentDidUpdate()
(3)卸载组件:由 ReactDOM.unmountComponentAtNode() 触发
① componentWillUnmount()
2.2 getDerivedStateFromProps(了解)
若 state 的值在任何时候都取决于 props,则可以使用 getDerivedStateFromProps。仅使用于罕见的案例,并且派生状态会导致状态冗余,了解即可。
官方文档说明:React.Component – React
2.3 getSnapshotBeforeUpdate(了解)
getSnapshotBeforeUpdate() 在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期方法的任何返回值将作为参数传递给 componentDidUpdate()。
返回 snapshot 的值(或 null)。
componentDidUpdate(prevProps, prevState, snapshot) { }
3、总结
旧生命周期 | 新生命周期 | |
初始化阶段 | ① constructor() ② componentWillMount() ③ render() ④ componentDidMount() | ① constructor() ② getDerivedStateFromProps() ③ render() ④ componentDidMount() |
更新阶段 | ① shouldComponentUpdate() ② componentWillUpdate() ③ render() ④ componentDidUpdate() | ① getDerivedStateFromProps() ② shouldComponentUpdate() ③ render() ④ getSnapshotBeforeUpdate() |
卸载组件 | ① componentWillUnmount() | ① componentWillUnmount() |
重要钩子 | ① render() 【必须使用】 ② componentDidMount() 【初始化】 ③ componentWillUnmount() 【收尾】 | |
新 / 旧区别 | ① 预计弃用 3 个:componentWillMount()、componentWillUpdate()、componentWillReceiveProps。如果要使用,需要加前缀 UNSAFE_。 ② 新增 2 个:getDerivedStateFromProps()、getSnapshotBeforeUpdate()。 |
四、DOM 的 diffing 算法
1、概述
在入门章节曾说过,React 有一个特点是:使用虚拟 DOM + Diffing 算法,尽量减少与真实 DOM 的交互。
主要步骤(个人理解):
(1)初始化的时候,虚拟 DOM 获取到了数据 A,并呈现到真实的 DOM 上。
(2)虚拟 DOM 再次接收到 数据 B 时,会先将数据 A 与 数据 B 进行对比,对比的最小力度是标签。
① 若标签内容没有发生任何改变,则不进行更新,直接沿用之前的。
② 若标签内容发生了改变,则将此时的数据(更新后的)呈现到页面中。
2、关于 key 的经典面试题
2.1 react / vue 中的 key 有什么作用?( key 的内部原理是什么?)
虚拟 DOM 中 key 的作用:
(1)简单的说:key 是虚拟 DOM 对象的标识,在更新显示时 key 起着极其重要的作用。
(2)详细的说:当状态中的数据发生变化时,react 会根据【新数据】生成【新的虚拟 DOM】,随后 React 进行【新虚拟 DOM】与【旧虚拟 DOM】的 diff 比较,比较规则如下:
① 旧虚拟 DOM 中找到了与新虚拟 DOM 相同的key:
a. 若虚拟 DOM 中内容没变,直接使用之前的真实 DOM。
b. 若虚拟 DOM 中内容变了,则生成新的真实 DOM 替换之前的 真实 DOM。
② 旧虚拟 DOM 中未找到与新虚拟 DOM 相同的 key,则根据数据创建新的真实 DOM,随后渲染到页面上。