 React - 基础与核心
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
 2
- Person 组件接收 - const {name,age,sex} = this.props1
 
对 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,替代类原来的 state
- render():挂载
- 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
