# React
什么是 React?
React 是一个用于构建用户界面的 JavaScript 库。React 主要用于构建 UI
为什么学习 React?
它引入了一种新的方式来处理浏览器 DOM。那些需要手动更新 DOM、费力地记录每一个状态的日子一去不复返了 —— 这种老旧的方式既不具备扩展性,又很难加入新的功能,就算可以,也是有着冒着很大的风险。React 使用很新颖的方式解决了这些问题。你只需要声明地定义各个时间点的用户界面,而无序关系在数据变化时,需要更新哪一部分 DOM。在任何时间点,React 都能以最小的 DOM 修改来更新整个应用程序。
特点如下
- 采用组件化模式、声明式编码,提高了开发效率以及组件复用率;
- 在 React Native 中可以使用 React 语法进行
移动端开发
;
- 使用
虚拟DOM
+ Diffing 算法,尽量减少与真实 DOM 的交互;
前置 JS 知识
判断 this 指向,class 类,ES6,npm 包,原型、原型链,数组操作,模块化
# jsx
1 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
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>hello react</title> </head> <body> <div id="demo">?</div> <script src="https://cdn.bootcss.com/react/16.4.0/umd/react.development.js"></script> <script src="https://cdn.bootcss.com/react-dom/16.4.0/umd/react-dom.development.js"></script> <script src="https://cdn.bootcss.com/babel-standalone/6.26.0/babel.min.js"></script>
<script type="text/babel"> const VDOM = <h1>Hello World</h1>; ReactDOM.render(VDOM, document.getElementById("demo")); </script> </body> </html>
|
需要注意的点:
-
<script> 标签内要写 ‘text/babel’
-
const VDOM = <h1>hello…<h1> 这里不要加 “” 或者 ‘’
为什么 React 要求用 JSX 而不是 JS ??
如果需要用到标签嵌套,jsx 更方便,而 js 创建虚拟 dom 会非常繁琐
# 真实 DOM 和虚拟 DOM
![image-20221130104457572]()
关于虚拟 DOM
1. 本质上是 Object 对象
2. 虚拟 DOM 比较 "轻", 真实 DOM 比较 "重", 因为虚拟 DOM 是 React 内部在用,无需真实 DOM 上那么多属性
3. 虚拟DOM最终会被React转化为真实DOM,呈现在页面上
# JSX 语法规则
# 组件化
# 安装插件
React Developer Tools
![image-20230205142139780]()
# 函数式组件
适用于简单组件 (无状态)
1. -组件- 首字母必须大写
1. 函数内的this会指向 undefined
1 2 3 4 5 6 7 8
| <script type="text/babel"> function Demo(){ console.log(this); return <h1>我是函数式定义组件</h1> } ReactDOM.render(<Demo/>,document.getElementById("jsx")); </script>
|
安装插件后,可以在开发者工具 查看组件
![image-20230205145004663]()
# 关于 react 元素调用函数
原生有三种
1 2 3 4 5 6 7 8 9
| const dom = document.getElementById('btn') dom.addEventListener('click',()=>{...})
const dom = document.getElementById('btn') dom.onclick= () = >{...}
<button onclick="demo()"/> function demo(){...}
|
React 中尽量使用第三种,并且需要注意, onClick
而不是 onclick
,要传入函数 {f} 而不是函数的调用
1 2 3 4 5
| render(){ return <h1 onClick={demo}>确认</h1> }
|
# 类式组件
适用于复杂组件 (有状态)
# 类的基础
1 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
| class Person { name; age; constructor(name, age) { this.name = name; this.age = age; }
speak() { console.log(`我叫${this.name},今年${this.age}岁`); } }
const p1 = new Person("小明", 18); const p2 = new Person("张三", 20);
p1.speak(); p2.speak();
p1.speak.call(new Person("王五", 19));
class Student extends Person { constructor(name, age, grade) { super(name, age); this.grade = grade; } study(){ console.log(`I am studying.. in ${this.grade}`) } } const s1 = new Student("老六",16,"高一"); s1.speak(); s1.study(); console.log(p1, p2 ,s1);
|
类中所定义的方法都是放在了类的原型对象上
- 原型 (prototype) 是函数的一个特殊属性,即指针指向
原型对象
。
- 原型对象 (prototype object) 是一个属于其所在
函数的空对象
,可以添加属性和方法。其自身 constructor 属性指向其函数
![image-20230205152727659]()
每个函数都有一个 prototype 属性。所有的 JavaScript 对象都会从对应 prototype(原型对象)中继承属性和方法.
如上图:
speak () 方法是放在原型对象里的
Person (小明…) 是 实例
第一个 Object 是 原型对象
Person (name,age) 是 构造函数
# 创建类式组件
1 2 3 4 5 6 7
| class Demo extends React.Component { render() { return <h1>我是类式定义组件,用于复杂组件</h1> } }
ReactDOM.render(<Demo />, document.getElementById("jsx"))
|
# 组件的 this
在组件内创建自定义方法时,this 为 undefined,如何解决?
- 强制绑定给实例对象,使用 bind ()
1
| this.tick = this.tick.bind(this)
|
- 使用箭头函数
# 组件三大属性
# state
当组件中的一些数据在某些时刻发生变化时,这时就需要使用 state
来跟踪状态。
state
和 props
之间最重要的区别是: props
由父组件传入,而 state
由组件本身管理。组件不能修改 props
,但它可以修改 state
。
对于所有变化数据中的每个特定部分,只应该由一个组件在其 state 中 “持有” 它。不要试图同步来自于两个不同组件的 state。相反,应当将其提升到最近的共同祖先组件中,并将这个 state 作为 props 传递到两个子组件。
看一个来自官方文档的 [时钟案例](Hello World in React (codepen.io))
1 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
| class Clock extends React.Component { constructor(props) { super(props); this.state = { date: new Date() }; }
tick() { this.setState({ date: new Date() }); }
componentDidMount() { this.timer = setInterval(() => this.tick(), 1000); }
componentWillUnmount() { clearInterval(this.timer); }
render() { return ( <div> <h2>现在时间</h2> <h2>It is {this.state.date.toLocaleTimeString()} now.</h2> </div> ); } }
ReactDOM.render(<Clock />, document.getElementById("jsx"));
|
让我们来快速概括一下发生了什么和这些方法的调用顺序:
- 当
<Clock />
被传给 ReactDOM.render()
的时候,React 会调用 Clock
组件的构造函数。因为 Clock
需要显示当前的时间,所以它会用一个包含当前时间的对象来初始化 this.state
。我们会在之后更新 state。
- 之后 React 会调用组件的
render()
方法。这就是 React 确定该在页面上展示什么的方式。然后 React 更新 DOM 来匹配 Clock
渲染的输出。
- 当
Clock
的输出被插入到 DOM 中后,React 就会调用 ComponentDidMount()
生命周期方法。在这个方法中, Clock
组件向浏览器请求设置一个计时器来每秒调用一次组件的 tick()
方法。
- 浏览器每秒都会调用一次
tick()
方法。 在这方法之中, Clock
组件会通过调用 setState()
来计划进行一次 UI 更新。得益于 setState()
的调用,React 能够知道 state 已经改变了,然后会重新调用 render()
方法来确定页面上该显示什么。这一次, render()
方法中的 this.state.date
就不一样了,如此以来就会渲染输出更新过的时间。React 也会相应的更新 DOM。
- 一旦
Clock
组件从 DOM 中被移除,React 就会调用 componentWillUnmount()
生命周期方法,这样计时器就停止了。
# props
当 React 元素为用户自定义组件时,它会将 JSX 所接收的属性(attributes)以及子组件(children)转换为单个对象传递给组件,这个对象被称之为 “props”。
props 传值语法糖
… 展开运算符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const p1 = {name: 'Tom',age: 17,sex: '女'} const p2 = {name: 'Jack',age: 18} function PersonListComponent(props){ return ( <ul> <li>姓名:{props.name}</li> <li>性别:{(props.sex)?props.sex:'男'}</li> {// 设置默认为男} <li>年龄:{props.age}</li> </ul> ) } function App(){ return( <div> <PersonListComponent {...p1} /> <PersonListComponent {...p2} /> </div> ); } ReactDOM.render(<App/>,document.getElementById("jsx"))
|
# refs
refs 实际上用途就是把节点 node 本身传到实例对象 obj 里面使用,一般需要通过一个元素访问另一个元素时使用 ref。
- 字符串 ref
1 2 3
| <h2 ref="title">{msg}</h2>
console.log(this.refs.title);
|
- 内联 ref
1 2
| <h2 ref={el => this.title = el}>{msg}</h2>
|
- 回调 ref
1 2 3
| <h2 ref={title}>{msg}</h2> title=(e)=>{ this.title = e }
|
- 创建 ref
1 2 3
| <h2 ref={myRef}>{msg}</h2> myRef = React.createRef();
|
# 字符串 refs (即将过时)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class Demo extends React.Component {
showData=()=>{ console.log(this) alert(this.refs.input1.value) }
showData2=()=>{ console.log(this.refs.input2) const {value} = this.refs.input2 alert(value) }
render() { return( <div> <input ref="input1" type="text" /> <button onClick={this.showData}>点击显示左侧数据</button> <input ref="input2" onBlur={this.showData2} placeholder="失去焦点显示数据" type="text" /> </div> ) } } ReactDOM.render(<Demo />, document.getElementById('jsx'))
|
字符串形式的 ref 中,ref 属性传值会生成标签的节点存储在实例对象 Demo的refs属性中
![image-20230206101104504]()
# 回调形式 refs (推荐)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| class Demo extends React.Component { showData = () => { alert(this.input1.value); console.log(this) };
showData2 = () => { const { value } = this.input2; alert(value); };
render() { return ( <div> <input ref={(e)=>{this.input1=e}} type='text' /> <button onClick={this.showData}>点击显示左侧数据</button> <input ref={e=>this.input2=e} onBlur={this.showData2} placeholder='失去焦点显示数据' type='text' /> </div> ); } } ReactDOM.render(<Demo />, document.getElementById("jsx"));
|
回调形式的 ref 中,ref 传数据不会存储在实例对象 Demo的refs属性中
,而是 直接挂载为实例对象Demo的属性
![image-20230206101333608]()
特别注意官网提到的 回调 ref 执行次数的问题
如果 ref
回调函数是以内联函数的方式定义的,在更新过程中它会被 执行两次
,第一次传入参数 null
,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个 新的函数实例
,所以 React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。
class 绑定函数的方式
1 2 3 4 5 6 7 8 9 10
| <input ref={(e)=>{this.input1=e}} type='text' />
<input ref={this.show} type='text' />
..Demo{ show=(e)=>{ this.input1=e alert(e.value) } }
|
# 创建 Refs (最推荐,v16 以上)
- 当
ref
属性用于 HTML 元素时,构造函数中使用 React.createRef()
创建的 ref
接收底层 DOM 元素作为其 current
属性。
1 2 3 4 5 6 7 8 9 10
| class MyComponent extends React.Component { myRef = React.createRefs() showData = () => { console.log(this.myRef) { alert(this.myRef.current.value) }; render() { return <div ref={this.myRef} />; } }
|
其实仔细读读官方文档发现写得挺好的,那就先写到这里了,跟着官方文档慢慢看吧。
# 表单
一定要学一下 formik
# 非受控组件
由 ref 携带表单 dom 然后获取 value 数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class Login extends React.Component { handleSubmit = (e) => { e.preventDefault(); alert(`账号是${this.username.value}密码是${this.password.value}`); }; render() { return ( <div> <form onSubmit={this.handleSubmit}> <input ref={(c) => (this.username = c)} name='username' type='text' /> <input ref={(c) => (this.password = c)} name='password' type='password' /> <button>登陆</button> </form> </div> ); } }
|
# 受控组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class Login extends React.Component { state = {username: '', password: ''}; handleSubmit = (e) => { e.preventDefault(); alert(`账户名是${this.state.username}密码是${this.state.password}`) }; saveUsername=(e)=>{ this.setState({username: e.target.value}); } savePassword=(e)=>{ this.setState({password: e.target.value}); } render() { return ( <div> <form onSubmit={this.handleSubmit}> <input onChange={this.saveUsername} name='username' type='text' /> <input onChange={this.savePassword} name='password' type='password' /> {//利用 onChange事件实现数据驱动的单向绑定} <button>登陆</button> </form> </div> ); } }
|
# 优化
以上例子中 saveUsername,savePassword 方法严重重复,如果还需要输入其他数据,就会变得很繁琐,将以上代码优化为:
1 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
| class Login extends React.Component { state = {username: '', password: ''}; handleSubmit = (e) => { e.preventDefault(); }; saveData=(event)=>{ const target = event.target const name = target.name this.setState({ [name]: target.value }) } render() { return ( <div> <form onSubmit={this.handleSubmit}> <label htmlFor='username'>用户名:</label> <input onChange={this.saveData} name='username' type='text' /> <label htmlFor='password'>密码:</label> <input onChange={this.saveData} name='password' type='password' /> <button>登陆</button> </form> </div> ); } }
|
# 组件生命周期
生命周期详细介绍参考 深入详解 React 生命周期
在之前的时钟案例中用到了 componentDidMount()
和 componentWillUnmount()
就是生命周期函数.
组件将要挂载时触发的函数:componentWillMount
组件挂载完成时触发的函数:componentDidMount
是否要更新数据时触发的函数:shouldComponentUpdate 不写默认返回 true
写了该函数不写返回值 则默认返回 undefined
将要更新数据时触发的函数:componentWillUpdate
数据更新完成时触发的函数:componentDidUpdate
组件将要销毁时触发的函数:componentWillUnmount
父组件中改变了 props 传值时触发的函数:componentWillReceiveProps 第一次加载不算,是接收 new props 才会算
React 旧版生命周期
新版生命周期