React - 基础与核心
# 三大核心
# state
作用:存储数据的容器。
写在 construct 里
export default class Demo extends Component{
construct(props){
super(props)
this.state = {
xxx:xxx
}
}
}
2
3
4
5
6
7
8
写在类里(推荐)
export default class Demo extends Component{
state = {
xxx:xxx
}
}
2
3
4
5
其他位置只需保证 this 是指向类的实例对象,即可通过 this.state.xxx
取到数据。
# props
作用
获得父组件传来的值。
通过便签传值:
key=value
传值对象传值
Person 的 props 里有三个属性:
ReactDOM.render(<Person name="jerry" age={19} sex="男"/>,document.getElementById('test1'))
1
方式
第一种:
key=value
传值- 缺点:当值多的时候,便签里的
key=value
代码不优雅
- 缺点:当值多的时候,便签里的
第二种:对象传值
通过
{...xxx}
传值前提:对象内的名字和接收的名字一致
const p = {name:'老刘',age:18,sex:'女'} ReactDOM.render(<Person {...p}/>,document.getElementById('test3'))
1
2Person 组件接收
const {name,age,sex} = this.props
1
对 props 的内容进行限制
写在类里面,需要使用 static 修饰
class Person extends React.Component{
// 对标签属性进行类型、必要性的限制
static propTypes = {
name:PropTypes.string.isRequired, // 限制 name 必传,且为字符串
sex:PropTypes.string, // 限制 sex 为字符串
age:PropTypes.number, // 限制 age 为数值
}
// 指定默认标签属性值
static defaultProps = {
sex:'男',// sex 默认值为男
age:18 // age 默认值为 18
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
写在类外面,通过类名 .xx
修饰
class Person extends React.Component{
// ......
}
// 对标签属性进行类型、必要性的限制
Person.propTypes = { // 固定
name:PropTypes.string.isRequired, // 限制 name 必传,且为字符串
sex:PropTypes.string, // 限制 sex 为字符串
age:PropTypes.number, // 限制 age 为数值
speak:PropTypes.func, // 限制 speak 为函数
}
// 指定默认标签属性值
Person.defaultProps = {
sex:'男', // sex 默认值为男
age:18 // age 默认值为 18
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
写在函数外面,通过函数名 .xx
修饰
function Person (props){
const {name,age,sex} = props
}
Person.propTypes = {
name:PropTypes.string.isRequired, // 限制 name 必传,且为字符串
sex:PropTypes.string, // 限制 sex 为字符串
age:PropTypes.number, // 限制 age 为数值
}
// 指定默认标签属性值
Person.defaultProps = {
sex:'男', // sex 默认值为男
age:18 // age 默认值为 18
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# ref
作用
存储标签,一般用于表单标签,获取表单标签的值。
使用
字符串形式(官方不推荐,影响效率)
在 x 标签内加入
ref="xxx"
,react 自动把xxx:x
放入 refs 中,通过this.refs.xxx
调用 x// 展示左侧输入框的数据 showData = ()=>{ const {input1} = this.refs alert(input1.value) } render(){ return( <div> <input ref="input1" type="text" placeholder="点击按钮提示数据"/> <button onClick={this.showData}>点我提示左侧的数据</button> </div> ) }
1
2
3
4
5
6
7
8
9
10
11
12
13
14回调形式。开发常用
在 x 标签使用
ref="e => this.xxx=e"
,其中 xxx 是一个回调函数,记得使用箭头函数把 this 指向类的实例e
是 react 自动放入的一个参数,值是当前标签,原理就是在类里创建一个 xxx,通过变量 = 值的方式,让xxx=e
此时在别的函数调用this.xxx
即可获得当前标签缺点:每次更新状态,调用 render 时,ref 会被调用两次,第一次的参数 e 为 null,第二次才是当前标签
原因:react 在重新调用 render 前,会把 render 内清除掉,此时再清除 xxx 时,发现有一个回调函数,所以把 null 传进去,第二次就是渲染页面,把当前标签传入
总结:状态 state 更新,render 先调用一次进行自我销毁,然后再调用一次进行新的渲染,第一次已经销毁,所以 e 为 null,第二次才是要求
该缺点官方明确说无关紧要
showData = ()=>{ const {input1} = this alert(input1.value) } render(){ return( <div> <input ref={e => this.input1 = e } type="text" placeholder="点击按钮提示数据"/> <button onClick={this.showData}>点我提示左侧的数据</button> </div> ) }
1
2
3
4
5
6
7
8
9
10
11
12函数形式
和回调形式一样,只不过把标签只是调用函数名,函数实现方式放到类里进行实现
ref="this.xxx"
,在类里实现 xxx 函数,xxx 函数会传入一个参数,该参数就是 xxx 所在的标签
createRef
创建 ref官方力推的 API
缺点:创建的 ref 只能存储一个标签,多个标签需要创建多个 ref
/* React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是「专人专用」的 */ myRef = React.createRef() // 展示左侧输入框的数据 showData = ()=>{ alert(this.myRef.current.value); } render(){ return( <div> <input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/> <button onClick={this.showData}>点我提示左侧的数据</button> <input onBlur={this.showData2} ref={this.myRef2} type="text" placeholder="失去焦点提示数据"/> </div> ) }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
总结
通过 onXxx 属性指定事件处理函数(注意大小写)
- React 使用的是自定义(合成)事件, 而不是使用的原生 DOM 事件:为了更好的兼容性
- React 中的事件是通过事件委托方式处理的(委托给组件最外层的元素):为了的高效
通过
event.target
得到发生事件的 DOM 元素对象:不要过度使用 ref- 如果需要一个标签事件响应另一个标签则使用 ref
- 如果事件响应仅在当前标签,该事件的第一个参数就是当前标签的事件对象,而
event.target
就是当前标签
# 概念基础
# 虚拟 DOM
学习完三大基础,现在了解 React 的核心:虚拟 DOM。
虚拟 DOM:React 把虚拟 DOM 渲染为真实 DOM。
举例
假设原生 DOM 是 a 和 b,在次基础上加上 c,则原生 DOM 是把原来的 a 和 b 去掉,重新加入 a 和 b 和 c。
但是虚拟 DOM 会去找是否有 a 和 b,有的话则直接在后面追加 c,无需去掉 a 和 b。
总结
原生 DOM:替换原来的元素:100 个元素变成 101 个元素(加 1 个元素),则前面的 100 个元素先去掉,再加上 101 个元素,即需要渲染 101 次,不判断是否重复,效率低
虚拟 DOM:增加新的元素:100 元素变成 101 元素(加 1 个元素),先判断前面的 100 元素一样,则需要渲染 1 次,因为前面 100 元素已经有了
# 函数组件
创建一个函数,元素在函数里进行 return,最后在 render 渲染。
// 1.创建函数式组件
function MyComponent(){
console.log(this); // 此处的 this 是 undefined,因为 babel 编译后开启了严格模式
return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>
}
// 2.渲染组件到页面
ReactDOM.render(<MyComponent/>,document.getElementById('test'))
/*
执行了 ReactDOM.render(<MyComponent/>....... 之后,发生了什么?
1.React 解析组件标签,找到了 MyComponent 组件。
2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟 DOM 转为真实 DOM,随后呈现在页面中。
*/
2
3
4
5
6
7
8
9
10
11
12
# 类式组件
创建一个类,元素在函数里进行 return,最后在 render 渲染。
class MyComponent extends React.Component {
render(){
// render 是放在哪里的?—— MyComponent 的原型对象上,供实例使用。
// render 中的 this 是谁?—— MyComponent 的实例对象 <=> MyComponent 组件实例对象。
console.log('render中的this:',this);
return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>
}
}
// 2.渲染组件到页面
ReactDOM.render(<MyComponent/>,document.getElementById('test'))
/*
执行了 ReactDOM.render(<MyComponent/>....... 之后,发生了什么?
1.React 解析组件标签,找到了 MyComponent 组件。
2.发现组件是使用类定义的,随后 new 出来该类的实例,并通过该实例调用到原型上的 render 方法。
3.将 render 返回的虚拟 DOM 转为真实 DOM,随后呈现在页面中。
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 受控组件
受控组件:现存 现取 现用,不依赖状态 state。
例子
直接在标签里存入 input 数据,不存放到 state。
直接在表单提交的时候,去获取 input 数据,并上传
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>1_非受控组件</title>
</head>
<body>
<!-- 准备好一个「容器」 -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
//创建组件
class Login extends React.Component{
handleSubmit = (event)=>{
event.preventDefault() //阻止表单提交
const {username,password} = this
alert(`你输入的用户名是:${username.value},你输入的密码是:${password.value}`)
}
render(){
return(
<form onSubmit={this.handleSubmit}>
用户名:<input ref={c => this.username = c} type="text" name="username"/>
密码:<input ref={c => this.password = c} type="password" name="password"/>
<button>登录</button>
</form>
)
}
}
//渲染组件
ReactDOM.render(<Login/>,document.getElementById('test'))
</script>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
例子
非受控组件:先存入状态 state 里,然后去状态 state 里获取数据,上传数据
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>2_受控组件</title>
</head>
<body>
<!-- 准备好一个「容器」 -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
//创建组件
class Login extends React.Component{
//初始化状态
state = {
username:'', //用户名
password:'' //密码
}
//保存用户名到状态中
saveUsername = (event)=>{
this.setState({username:event.target.value})
}
//保存密码到状态中
savePassword = (event)=>{
this.setState({password:event.target.value})
}
//表单提交的回调
handleSubmit = (event)=>{
event.preventDefault() //阻止表单提交
const {username,password} = this.state
alert(`你输入的用户名是:${username},你输入的密码是:${password}`)
}
render(){
return(
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={this.saveUsername} type="text" name="username"/>
密码:<input onChange={this.savePassword} type="password" name="password"/>
<button>登录</button>
</form>
)
}
}
//渲染组件
ReactDOM.render(<Login/>,document.getElementById('test'))
</script>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# 生命周期
mount(挂载)
react调用render方法,把虚拟DOM解析为真实DOM,挂载到页面上
所以生命周期就是围绕挂载前后进行
unmount(卸载)
如果需要把render挂载的页面清除掉,则需要卸载
# 旧版生命周期
初始化阶段: 由 ReactDOM.render()
触发(初次渲染),依次触发顺序:
constructor()
:构造器componentWillMount()
:组件将要挂载render()
:挂载componentDidMount()
:组件已经挂载,常用 的钩子函数,did 为 do 的过去式一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息。
更新阶段: 由组件内部 this.setSate()
或父组件 render 触发,依次触发顺序
componentWillReceiveProps
:组件将收到父组件的参数,注意:第二次以上父组件传参才被调用,第一次指挂载页面的时候,不会触发shouldComponentUpdate()
:组件是否更新,在方法里需要只当返回值为布尔类型,false 则不会执行下面操作 234,重写该方法必须指定布尔值componentWillUpdate()
:组件将要更新render()
:挂载更新,必须使用 的一个钩子函数componentDidUpdate()
:组件已经更新,did 为 do 的过去式
卸载组件: 由 ReactDOM.unmountComponentAtNode()
触发,依次触发顺序:
componentWillUnmount()
:需要一个函数调用该函数,参数为卸载的组件,该组件的所有内容包括自己被清除,常用一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息。
// 卸载组件按钮的回调 death = ()=>{ ReactDOM.unmountComponentAtNode(document.getElementById('test')) } <button onClick={this.death}>卸载组件</button>
1
2
3
4
5
# 新版生命周期
初始化阶段: 由 ReactDOM.render()
触发(初次渲染),依次触发顺序:
constructor():构造器
getDerivedStateFromProps
:从父组件传来的 props 里获取 state,替代类原来的 staterender()
:挂载componentDidMount()
:组件已经挂载,常用 的钩子函数,did 为 do 的过去式一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
更新阶段: 由组件内部 this.setSate()
或父组件重新 render 触发,依次触发顺序:
getDerivedStateFromProps:从父组件传来的 props 里获取 state,替代类原来的 state
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate()
:更新前从快照获取数据,提供更新原来的数据可以存储,需要返回值,返回给5的第三个参数里componentDidUpdate()
:页面更新完成后,有三个参数,默认第一个是更新前的 props,第二个是更新前的 state,第三个是 4 快照传来的值
卸载组件: 由 ReactDOM.unmountComponentAtNode()
触发,依次触发顺序:
componentWillUnmount()
:常用一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息。
新函数钩子举例
// 若 state 的值在任何时候都取决于 props,那么可以使用 getDerivedStateFromProps
static getDerivedStateFromProps(props,state){
console.log('getDerivedStateFromProps',props,state);
return null
}
// 在更新之前获取快照
getSnapshotBeforeUpdate(){
console.log('getSnapshotBeforeUpdate');
return 'kele' // 返回给 componentDidUpdate()
}
// 组件更新完毕的钩子
// 更新前的 props,更新前的 state,以及 getSnapshotBeforeUpdate 的返回值
componentDidUpdate(preProps,preState,snapshotValue){
console.log('Count---componentDidUpdate',preProps,preState,snapshotValue);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
getSnapshotBeforeUpdate
的使用场景:
模拟新闻
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>4_getSnapShotBeforeUpdate的使用场景</title>
<style>
.list{
width: 200px;
height: 150px;
background-color: skyblue;
overflow: auto;
}
.news{
height: 30px;
}
</style>
</head>
<body>
<!-- 准备好一个「容器」 -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/17.0.1/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/17.0.1/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/17.0.1/babel.min.js"></script>
<script type="text/babel">
class NewsList extends React.Component{
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
}
componentDidUpdate(preProps,preState,height){
this.refs.list.scrollTop += this.refs.list.scrollHeight - height
}
render(){
return(
<div className="list" ref="list">
{
this.state.newsArr.map((n,index)=>{
return <div key={index} className="news">{n}</div>
})
}
</div>
)
}
}
ReactDOM.render(<NewsList/>,document.getElementById('test'))
</script>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68