总结
一、非受控组件与受控组件
- 非受控组件
表单项不与state数据相向关联, 需要手动读取表单元素的值
借助于 ref获取真实DOM,在通过value获得输入值
,使用原生 DOM 方式来获取表单元素值
非受控组件: 表单项不与 state 数据相向关联, 需要手动读取表单元素的值
- 受控组件
组件中的表单项根据state状态
数据动态初始显示和更新显示, 当用户输入时实时同步到状态数据中
也就是实现了页面表单项与 state 数据的双向绑定
实现方式
- 在 state 中添加一个状态,作为表单元素的value值(控制表单元素值的来源)
- 给表单元素绑定 change 事件,将 表单元素的值 设置为 state 的值(控制表单元素值的变化)
{/* react中虚拟DOM中的name的值一定是和state属性相关的值 */}
<p>用户名:<input type="text" name='user' value={user} onChange={this.changeForm} /></p>
{/* a是咱们自定义的属性名,后期获取的时候需要使用getAttribute()方法 */}
<p>用户名:<input type="text" a='user' value={user} onChange={this.changeForm} /></p>
{/* 在DOM元素对象身上还可以通过data-a的形式添加,后期获取的时候使用dataset.a */}
<p>用户名:<input type="text" data-a='user' value={user} onChange={this.changeForm} /></p>
changeForm = (e) => {
this.setState({
[e.target.name]: e.target.value
[e.target.getAttribute('a')]: e.target.value
[e.target.dataset.a]: e.target.value
})
}
二、高阶函数
本身也是一个函数
特点:接收函数类型的参数或者
返回一个新函数
function fn(f){
return ()=>{
}
}
fn1(()=>{})
const p = new Promise((resolve,reject)=>{});
then(value=>{},reason=>{})
p.catch(reason=>{})
setTimeout(()=>{}) setInterval(()=>{})
let arr = [1,2,3];
arr.foreach(()=>{})
arr.map((item,index)=>{})
函数类型参数:Promise、then、catch、setTimeout、setInterval、forEach、map、filter、every、some等
返回函数:闭包函数、bind
function fun(a, b) {
function fun1() {
a++;
return a;
}
return fun1;
}
fun(10,20);
<p>用户名:<input type="text" value={user} onChange={this.saveData('user')} /></p>
saveData = (type) => {
return (e) => {
this.setState({
[type]: e.target.value
})
}
}
三、React脚手架
1、 脚手架的安装与启动
- 全局安装
npm i -g create-react-app
- 切换目录
create-react-app 目录名称
- 进入目录
cd 目录名称
- 启动项目
npm start
2、脚手架目录说明
- public 脚手架服务的网站根目录(静态资源目录)
- index.html :webpack打包html的模板文件
- src 源代码开发的目录
- index.js 打包的入口文件
- .gitignore git的配置忽略文件
3、 props 校验 (了解)
允许在创建组件的时候,就指定 props 的类型、格式等
作用:捕获使用组件时因为props导致的错误,给出明确的错误提示,增加组件的健壮性
实现方式:
- 导入 prop-types 包 (脚手架自带,无需额外安装)
- 使用propTypes来给组件的props添加校验规则
4、 props 设置类型和设置默认值
作用:给 props 设置默认值,在未传入 props 时生效
在class类中实现方式:
import React, { Component } from 'react'
//1、导包
import types from 'prop-types'
export default class Stu extends Component {
//2、在类内使用静态属性 propTypes 来约束
//注意:这个属性不能修改
static propTypes = {
name: types.string.isRequired,
age: types.number,
hobby: types.array
}
//3、为传入的属性设置默认值
static defaultProps = {
name: '李四',
age: 24
}
render() {
let { name, age, hobby } = this.props;
return (
<div>
<p>姓名:{name}</p>
<p>年龄:{age}</p>
<p>爱好:{hobby.join(',')}</p>
</div>
)
}
}
5、函数式组件中使用props校验
App.jsx
import Stu from './Stu'
export default function App() {
//声明一个对象
let info = {
// name: '张三',
age: 23,
hobby: ['吃饭', '睡觉', '打豆豆']
}
return (
<div>
<Stu {...info} />
</div>
)
}
Stu.jsx
//1、导包
import PropTypes from 'prop-types'
//2、为Stu设置propTypes属性
Stu.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
hobby: PropTypes.array
}
Stu.defaultProps = {
name: '王二麻子'
}
export default function Stu(props) {
let { name, age, hobby } = props;
return (
<div>
<p>姓名:{name}</p>
<p>年龄:{age}</p>
<p>爱好:{hobby.join('-')}</p>
</div>
)
}
四、React组件的生命周期
组件对象从创建到死亡所经历的特定的阶段
React组件对象包含一系列钩子函数(生命周期回调函数),在特定的时刻调用
我们在定义组件时,在特定的生命周期回调函数中做特定的工作
全称:生命周期函数或者是生命周期钩子,其中主要研究的就是组件生命周期的三个阶段
概念:生命周期函数指在某一时刻组件会自动调用执行的函数
本质
就是组件内的一些方法
,当然在这里咱们讨论的是类式组件
,因为函数式组件中有相应的Hook函数
特点
就是能够自动执行
其中包括ComponentDidMount()
、ComponentDidUpdate()
、ComponentWillUnmount()
1.1 挂载阶段
流程: constructor ==> render ==> componentDidMount
子父类组件父组件渲染:父构造->父render->子构造->子render->子完成挂载->父完成挂载
父constructor =>父render=>子constructor =>子render=>子componentDidMount===>父componentDidMount
触发: ReactDOM.render(): 渲染组件元素
-
constructor: 创建组件时,最先执行
一般用于:
-
- 初始化state
-
-
为事件处理程序绑定this
this.xx = this.xx.bind(this)
-
-
-
render: 每次组件渲染都会触发
- 注意: 不能在render中调用setState()
-
componentDidMount: 组件挂载(完成DOM)渲染后
注意:
这个生命周期钩子从虚拟DOM转化成真实DOM之后,挂载在root元素之后,只执行这么一次
除非这个组件被卸载之后,重新挂载,还会在此执行,且还是一次
一般用于:
-
- 定时器
- 发送网络请求 axios
- 订阅频道
-
- DOM操作 (只要是组件一上来就要做的事情,都应该写在挂载成功的回调中)
-
1.2 更新阶段
componentDidUpdate: 组件更新(完成DOM渲染)后
流程: render ==> componentDidUpdate
触发: setState() , 组件接收到新的props
如果是父组件中嵌套子组件,
父组件中的状态被更新了:
父组件render->子组件render->子组件componentDidUpdate->…->父组件componentDidUpdate
子组件中的状态被更新了:那么只有这个子组件中的render->这个子组件中的componentDidUpdate执行
1.3 卸载阶段
componentWillUnmount: 组件卸载(从页面中消失) 执行清理操作
测试:当我们更改组件内容并保存之后,React会将之前的组件卸载并重新挂载一个新的组件
顺序:自己的构造->自己的render->卸载钩子函数->重新挂载
constructor->rende->componentWillUnmount->componentDidMount
父组件内容变化时触发
子组件内容变化触发:
例如:
- 清空定时器
- 取消订阅
流程: componentWillUnmount
触发: 不再渲染组件
五、Hook
- Hook 是 React 16.8 之后的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性
- Hook 也叫钩子,
本质就是函数
,能让你在函数式组件
中使用状态
和生命周期函数
等功能 - Hook 语法 基本已经代替了类组件的语法,后面的 React 项目就完全是用 Hook 语法了
5.1 useState
用来定义状态数据,可以多次调用
, 产生多个状态数据
mport React, { useState } from 'react'
export default function App() {
// console.log(React.useState);
/**
* React.useState()方法返回一个数组,
* 数组中的第一个元素记录state中的状态数据
* 数组中的第二个元素是一个方法,用来修改状态数据,类似于setState
*/
//对象解构赋值
//注意:React.useState()方法可以在导入react的时候直接做解构,后面就可以直接使用useState()方法
let [isLogin, setLogin] = useState(true);
function changeLogin() {
setLogin(!isLogin);
}
return (
<div>
<p>现在的状态:{isLogin ? '已登录' : "未登录"}</p>
{/* 按钮中直接调用函数,不需要写this */}
<button onClick={changeLogin}>修改状态</button>
</div >
)
}
5.2 useRef
功能和类式组件中的createRef()方法相似
,可以帮助我们在函数式组件中方便获取真实的DOM元素对象。
import React, { useRef } from 'react'
export default function App() {
let ipt = useRef();
let box = useRef();
let show = () => {
let v = ipt.current.value;
box.current.innerHTML += '用户名:' + v + '<br/>';
}
return (
<div>
<p>
用户名:<input type="text" ref={ipt} />
<button onClick={show}>获取</button>
</p>
<hr />
<div id="result" ref={box} style={{ width: 500, height: 500, border: '1px dashed #ccc' }}></div>
</div>
)
}
5.3 useEffect
是一个高阶函数,可以在一个组件中多次使用
,相当于componentDidMount(组件挂载完成),
componentDidUpdate(组件更新完成) 和 componentWillUnmount(组件将要卸载之前)
的组合
语法:React.useEffect(()=>{},[])
当如果不希望在组件更新时再次执行,只希望在组件挂载时执行,则需要给useEffect方法设置第二个参数为数组
数组
参数的含义:设置了哪些状态数据修改之后,才会执行回调,
分为两种情况:
- 数组的参数如果没有任何值的话,表示什么状态数据都不更新,回调函数不执行
- 当数组中传入state的状态数据,表示当这个状态数据被更新了的时候,才执行回调函数
import React, { useEffect, useState } from 'react'
export default function App() {
//声明状态
let [count, setCount] = useState(0);
let [sex, setSex] = useState(true);
//调用
//第一个参数为函数,箭头函数或匿名函数均可
//这个函数的作用等效于ComponentDidMount和ComponentDidUpdate
//这个方法可以执行多次
useEffect(function () {
console.log('我既执行ComponentDidMount效果,也执行ComponentDidUpdate效果-1');
});
useEffect(function () {
console.log('我既执行ComponentDidMount效果,也执行ComponentDidUpdate效果-2');
});
//当如果只想要ComponentDidMount效果,则需要给useEffect方法传入第二个参数,为数组
//数组的参数如果没有任何元素的话,表示不更新状态
/* useEffect(() => {
console.log('我只执行ComponentDidMount效果');
}, []) */
//如果想要某一个状态数据改变的时候,需要执行,则将这个状态数据添加即可
/* useEffect(() => {
console.log('我只执行ComponentDidMount效果');
}, [sex]) */
//如果想要模拟ComponentwillUnmount
//在脚手架中当尝试修改组件之后,react会将组件先卸载,然后再来一个新的重新挂载
useEffect(() => {
return () => {
console.log('组件将要被卸载时...')
}
})
let changeAdd = (num) => {
return () => {
count = count + num;
setCount(count);
}
}
let changeSex = () => {
setSex(!sex);
}
return (
<div>
<ul>
<li>
<p>数值:{count}</p>
<button onClick={changeAdd(10)}>新增</button>
</li>
</ul>
<ul>
<li>
<p>性别:{sex ? '男' : '女'}</p>
<button onClick={changeSex}>修改</button>
</li>
</ul>
</div>
)
}
但是使用useEffect()方法的时候有一个很重要的细节
,那就是useEffect方法的第一个函数不能是一个异步函数
也就是说,不能出现async
,原因是如果设置了async,返回值就成了promise对象,非一个函数了。
如果想要在useEffect()方法中添加异步代码,需要在函数中在单独声明一个函数
//如果想要模拟ComponentwillUnmount
//在脚手架中当尝试修改组件之后,react会将组件先卸载,然后再来一个新的重新挂载
useEffect(() => {
return () => {
async function main() {
console.log('在这里可以执行异步代码逻辑...');
}
main();
}
})
使用Hook 注意:
只在最顶层使用 Hook,不要在条件或循环中
只在React组件函数内部中调用 Hook, 不要在组件函数外部调用
React全家桶
一、非受控组件与受控组件
- 非受控组件
表单项不与state数据相向关联, 需要手动读取表单元素的值
借助于 ref获取真实DOM,在通过value获得输入值
,使用原生 DOM 方式来获取表单元素值
非受控组件: 表单项不与 state 数据相向关联, 需要手动读取表单元素的值
编码过程
1、初始化创建 ref 容器并保存到组件对象上
2、将 ref 容器通过 ref 属性交给表单项标签 ,渲染时内部会将对应的真实DOM保存到 ref 容器的 current属性上
3、点击提交按钮时, 通过 ref 容器的 current 属性得到 input DOM 元素 => 就可以读取其 value了
不足:不够自动化/无法进行实时数据校验
先来看一个案例:
import React, { Component } from 'react'
export default class App extends Component {
//创建真实DOM容器
user = React.createRef();
pass = React.createRef();
render() {
return (
<div>
<form>
<h3>登录页面(非受控组件)</h3>
<p>用户名:<input type="text" ref={this.user} /></p>
<p>密码:<input type="password" ref={this.pass} /></p>
<p><button onClick={this.show}>登录</button></p>
</form>
</div>
)
}
show = (e) => {
//禁止表单提交
e.preventDefault();
//获取输入的内容
let username = this.user.current.value;
let userpass = this.pass.current.value;
//输出内容
console.log(`当前的用户名为${username},密码为${userpass}`);
}
}
- 受控组件
组件中的表单项根据state状态
数据动态初始显示和更新显示, 当用户输入时实时同步到状态数据中
也就是实现了页面表单项与 state 数据的双向绑定
后期使用颇多
实现方式
- 在 state 中添加一个状态,作为表单元素的value值(控制表单元素值的来源)
- 给表单元素绑定 change 事件,将 表单元素的值 设置为 state 的值(控制表单元素值的变化)
import React, { Component } from 'react';
class App extends Component {
state = {
//创建两个初始状态值
user: '',
pass: ''
}
render() {
return (
<div>
<form>
<h3>登录页面(受控组件)</h3>
<p>用户名:<input type="text" value={this.state.user} onChange={this.saveUser} /></p>
<p>密码:<input type="password" value={this.state.pass} onChange={this.savePass} /></p>
<p><button onClick={this.login}>登录</button></p>
</form>
</div>
);
}
login = (e) => {
e.preventDefault();
console.log(this.state.user);
console.log(this.state.pass);
}
saveUser = (e) => {
this.setState({
user: e.target.value
})
}
savePass = (e) => {
this.setState({
pass: e.target.value
})
}
}
export default App;
优化1: 使用同一个事件函数处理
问题: 2个input的onChange事件处理函数代码重复
解决: 使用一个事件函数
import React, { Component } from 'react';
class App extends Component {
state = {
//创建两个初始状态值
user: '',
pass: '',
phone: ''
}
render() {
const { user, pass, phone } = this.state;
return (
<div>
<form>
<h3>登录页面(受控组件)</h3>
{/** 注意这里表单元素的name属性可以自定义,但是属性值必须是来自于state中的属性名,否则后期无法更新 */}
<p>用户名:<input type="text" name='user' value={user} onChange={this.saveData} /></p>
<p>密码:<input type="password" name='pass' value={pass} onChange={this.saveData} /></p>
<p>手机号:<input type="text" name='phone' value={phone} onChange={this.saveData} /></p>
<p><button onClick={this.login}>登录</button></p>
</form>
</div>
);
}
saveData = (e) => {
this.setState({
[e.target.name]: e.target.value
})
}
login = (e) => {
e.preventDefault();
console.log(this.state.user);
console.log(this.state.pass);
console.log(this.state.phone);
}
}
export default App;
import React, { Component } from 'react'
export default class App extends Component {
//在实例对象身上修改一个state属性
state = {
user: '',
pass: ''
}
render() {
const { user, pass } = this.state;
return (
<div>
<form>
<h3>登录页面(非受控组件)</h3>
{/* react中虚拟DOM中的name的值一定是和state属性相关的值 */}
<p>用户名:<input type="text" name='user' value={user} onChange={this.changeForm} /></p>
{/* a是咱们自定义的属性名,后期获取的时候需要使用getAttribute()方法 */}
<p>用户名:<input type="text" a='user' value={user} onChange={this.changeForm} /></p>
{/* 在DOM元素对象身上还可以通过data-a的形式添加,后期获取的时候使用dataset.a */}
<p>用户名:<input type="text" data-a='user' value={user} onChange={this.changeForm} /></p>
<p>密码:<input type="password" data-a='pass' value={pass} onChange={this.changeForm} /></p>
<p><button onClick={this.login}>登录</button></p>
</form>
</div>
)
}
login = (e) => {
e.preventDefault();
console.log(e.target)
console.log('用户名:', this.state.user)
console.log('密码:', this.state.pass)
}
changeForm = (e) => {
this.setState({
// [e.target.getAttribute('a')]: e.target.value
[e.target.dataset.a]: e.target.value
})
}
}
二、高阶函数
本身也是一个函数
特点:接收函数类型的参数或者
返回一个新函数
function fn(f){
return ()=>{
}
}
fn1(()=>{})
const p = new Promise((resolve,reject)=>{});
then(value=>{},reason=>{})
p.catch(reason=>{})
setTimeout(()=>{}) setInterval(()=>{})
let arr = [1,2,3];
arr.foreach(()=>{})
arr.map((item,index)=>{})
函数类型参数:Promise、then、catch、setTimeout、setInterval、forEach、map、filter、every、some等
返回函数:闭包函数、bind
function fun(a, b) {
function fun1() {
a++;
return a;
}
return fun1;
}
fun(10,20);
import React, { Component } from 'react';
class App extends Component {
state = {
//创建两个初始状态值
user: '',
pass: '',
phone: ''
}
render() {
const { user, pass, phone } = this.state;
return (
<div>
<form>
<h3>登录页面(受控组件)</h3>
{/** 注意这里表单元素的name属性可以自定义,但是属性值必须是来自于state中的属性名,否则后期无法更新 */}
<p>用户名:<input type="text" value={user} onChange={this.saveData('user')} /></p>
<p>密码:<input type="password" value={pass} onChange={this.saveData('pass')} /></p>
<p>手机号:<input type="text" value={phone} onChange={this.saveData('phone')} /></p>
<p><button onClick={this.login}>登录</button></p>
</form>
</div>
);
}
saveData = (type) => {
return (e) => {
this.setState({
[type]: e.target.value
})
}
}
login = (e) => {
e.preventDefault();
console.log(this.state.user);
console.log(this.state.pass);
console.log(this.state.phone);
}
}
export default App;
三、React脚手架
3.1 脚手架的介绍
React 脚手架是官方提供的 React 开发工具。
脚手架开发项目的特点:
- 更安全
- 包含了 eslint 配置,实时提醒代码异常
- 更方便
- 包含了所有需要的配置(jsx编译、devServer…)
- 下载好了 react 的依赖包(react,react-dom,babel…)
- 搭建好了目录结构
- 更高效
- 实时预览代码运行效果
- 打包优化处理
3.2 npm安装源的工具包
nrm
安装
npm i nrm -g
查看源列表
nrm ls
修改npm的安装源
nrm use taobao
3.3 脚手架的安装与启动
-
全局安装
npm i -g create-react-app
-
切换目录
create-react-app 目录名称
-
进入目录
cd 目录名称
-
启动项目
npm start
【注:当下载过程中卡住了,可以ctrl+c停止,并且将下载一部分的文件夹删除并重新运行创建项目命令】
3.4 脚手架目录说明
- public 脚手架服务的网站根目录(静态资源目录)
- index.html :webpack打包html的模板文件
- src 源代码开发的目录
- index.js 打包的入口文件
- .gitignore git的配置忽略文件
3.5 props 校验 (了解)
对于组件来说,props 是外来的,无法保证组件使用者传入什么格式的数据
如果传入的数据格式不对,可能会导致组件内部报错
关键问题:组件的使用者不知道明确的错误原因
允许在创建组件的时候,就指定 props 的类型、格式等
作用:捕获使用组件时因为props导致的错误,给出明确的错误提示,增加组件的健壮性
实现方式:
- 导入 prop-types 包 (脚手架自带,无需额外安装)
- 使用propTypes来给组件的props添加校验规则
3.6 props 设置类型和设置默认值
作用:给 props 设置默认值,在未传入 props 时生效
在class类中实现方式:
import React, { Component } from 'react'
//1、导包
import types from 'prop-types'
export default class Stu extends Component {
//2、在类内使用静态属性 propTypes 来约束
//注意:这个属性不能修改
static propTypes = {
name: types.string.isRequired,
age: types.number,
hobby: types.array
}
//3、为传入的属性设置默认值
static defaultProps = {
name: '李四',
age: 24
}
render() {
let { name, age, hobby } = this.props;
return (
<div>
<p>姓名:{name}</p>
<p>年龄:{age}</p>
<p>爱好:{hobby.join(',')}</p>
</div>
)
}
}
3.7 函数式组件中使用props校验
App.jsx
import Stu from './Stu'
export default function App() {
//声明一个对象
let info = {
// name: '张三',
age: 23,
hobby: ['吃饭', '睡觉', '打豆豆']
}
return (
<div>
<Stu {...info} />
</div>
)
}
Stu.jsx
//1、导包
import PropTypes from 'prop-types'
//2、为Stu设置propTypes属性
Stu.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
hobby: PropTypes.array
}
Stu.defaultProps = {
name: '王二麻子'
}
export default function Stu(props) {
let { name, age, hobby } = props;
return (
<div>
<p>姓名:{name}</p>
<p>年龄:{age}</p>
<p>爱好:{hobby.join('-')}</p>
</div>
)
}
四、React组件的生命周期
组件对象从创建到死亡所经历的特定的阶段
React组件对象包含一系列钩子函数(生命周期回调函数),在特定的时刻调用
我们在定义组件时,在特定的生命周期回调函数中做特定的工作
全称:生命周期函数或者是生命周期钩子,其中主要研究的就是组件生命周期的三个阶段
概念:生命周期函数指在某一时刻组件会自动调用执行的函数
本质
就是组件内的一些方法
,当然在这里咱们讨论的是类式组件
,因为函数式组件中有相应的Hook函数
特点
就是能够自动执行
其中包括ComponentDidMount()
、ComponentDidUpdate()
、ComponentWillUnmount()
1.1 挂载阶段
流程: constructor ==> render ==> componentDidMount
子父类组件父组件渲染:父构造->父render->子构造->子render->子完成挂载->父完成挂载
父constructor =>父render=>子constructor =>子render=>子componentDidMount===>父componentDidMount
触发: ReactDOM.render(): 渲染组件元素
-
constructor: 创建组件时,最先执行
一般用于:
-
- 初始化state
-
-
为事件处理程序绑定this
this.xx = this.xx.bind(this)
-
-
-
render: 每次组件渲染都会触发
- 注意: 不能在render中调用setState()
-
componentDidMount: 组件挂载(完成DOM)渲染后
注意:
这个生命周期钩子从虚拟DOM转化成真实DOM之后,挂载在root元素之后,只执行这么一次
除非这个组件被卸载之后,重新挂载,还会在此执行,且还是一次
一般用于:
-
- 定时器
- 发送网络请求 axios
- 订阅频道
-
- DOM操作 (只要是组件一上来就要做的事情,都应该写在挂载成功的回调中)
-
1.2 更新阶段
componentDidUpdate: 组件更新(完成DOM渲染)后
流程: render ==> componentDidUpdate
触发: setState() , 组件接收到新的props
如果是父组件中嵌套子组件,
父组件中的状态被更新了:
父组件render->子组件render->子组件componentDidUpdate->…->父组件componentDidUpdate
子组件中的状态被更新了:那么只有这个子组件中的render->这个子组件中的componentDidUpdate执行
1.3 卸载阶段
**componentWillUnmount:**组件卸载(从页面中消失) 执行清理操作
测试:当我们更改组件内容并保存之后,React会将之前的组件卸载并重新挂载一个新的组件
顺序自己的构造->自己的render->卸载钩子函数->重新挂载
constructor->rende->componentWillUnmount->componentDidMount
父组件内容变化时触发
子组件内容变化触发:
例如:
- 清空定时器
- 取消订阅
流程: componentWillUnmount
触发: 不再渲染组件
案例1:电子时钟
Timer.jsx
import React, { Component } from 'react';
import moment from 'moment';
export default class Life extends Component {
constructor() {
super();
console.log('执行构造器初始化组件实例对象属性')
this.state = {
timestr: moment().format("YYYY-MM-DD HH:mm:ss")
}
this.timer = null;
}
componentDidMount() {
console.log('组件挂载完成后执行....')
this.timer = setInterval(() => {
this.setState({
timestr: moment().format("YYYY-MM-DD HH:mm:ss")
})
}, 1000)
}
componentWillUnmount() {
console.log('组件将要被卸载的时候')
clearInterval(this.timer)
}
render() {
console.log('指定render方法了....');
let { timestr } = this.state;
return (
<div style={{ width: 200, height: 30, border: '1px dashed #ccc', lineHeight: '30px', textAlign: 'center' }}>
{timestr}
</div>
);
}
}
案例2:消息列表
import React, { Component, createRef } from 'react'
export default class Message extends Component {
ipt = createRef();
state = {
messages: [
'天王盖地虎',
'小鸡炖蘑菇',
'宝塔镇河妖',
'蘑菇放辣椒'
]
}
sendMessage = () => {
let v = this.ipt.current.value;
this.setState({
messages: [...this.state.messages, v]
})
this.ipt.current.value = ''
}
render() {
let { messages } = this.state;
return (
<div>
<input type="text" ref={this.ipt} />
<button onClick={this.sendMessage}>发送消息</button>
<hr />
<div ref={el => this.box = el} style={{ width: 400, height: 300, border: '1px dashed #ccc', overflow: 'auto' }}>
<ul>
{messages.map((item, index) => {
return <li key={index}>{item}</li>
})}
</ul>
</div>
</div>
)
}
componentDidUpdate() {
console.log('组件中的状态更新了....');
this.box.scrollTop = 20000;
}
}
五、Hook
- Hook 是 React 16.8 之后的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性
- Hook 也叫钩子,
本质就是函数
,能让你在函数式组件
中使用状态
和生命周期函数
等功能 - Hook 语法 基本已经代替了类组件的语法,后面的 React 项目就完全是用 Hook 语法了
5.1 useState
用来定义状态数据,可以多次调用
, 产生多个状态数据
mport React, { useState } from 'react'
export default function App() {
// console.log(React.useState);
/**
* React.useState()方法返回一个数组,
* 数组中的第一个元素记录state中的状态数据
* 数组中的第二个元素是一个方法,用来修改状态数据,类似于setState
*/
//对象解构赋值
//注意:React.useState()方法可以在导入react的时候直接做解构,后面就可以直接使用useState()方法
let [isLogin, setLogin] = useState(true);
function changeLogin() {
setLogin(!isLogin);
}
return (
<div>
<p>现在的状态:{isLogin ? '已登录' : "未登录"}</p>
{/* 按钮中直接调用函数,不需要写this */}
<button onClick={changeLogin}>修改状态</button>
</div >
)
}
案例1:利用按钮增加数值
import React, { useState } from 'react'
export default function App() {
//由于数组中的解构,所以变量名可以进行自定义
let [count, setCount] = useState(0);
let changeCount = (num) => {
setCount(count + num)
}
return (
<div>
<p>点击次数:{count}</p>
<button onClick={e => changeCount(10)}>点击增加十次</button><br /><br />
<button onClick={changeCount(100)}>点击增加一百次</button>
</div>
)
}
5.2 useRef
功能和类式组件中的createRef()方法相似
,可以帮助我们在函数式组件中方便获取真实的DOM元素对象。
import React, { useRef } from 'react'
export default function App() {
let ipt = useRef();
let box = useRef();
let show = () => {
let v = ipt.current.value;
box.current.innerHTML += '用户名:' + v + '<br/>';
}
return (
<div>
<p>
用户名:<input type="text" ref={ipt} />
<button onClick={show}>获取</button>
</p>
<hr />
<div id="result" ref={box} style={{ width: 500, height: 500, border: '1px dashed #ccc' }}></div>
</div>
)
}
5.3 useEffect
是一个高阶函数,可以在一个组件中多次使用
,相当于componentDidMount(组件挂载完成),
componentDidUpdate(组件更新完成) 和 componentWillUnmount(组件将要卸载之前)
的组合
语法:React.useEffect(()=>{},[])
当如果不希望在组件更新时再次执行,只希望在组件挂载时执行,则需要给useEffect方法设置第二个参数为数组
数组
参数的含义:设置了哪些状态数据修改之后,才会执行回调,
分为两种情况:
- 数组的参数如果没有任何值的话,表示什么状态数据都不更新,回调函数不执行
- 当数组中传入state的状态数据,表示当这个状态数据被更新了的时候,才执行回调函数
import React, { useEffect, useState } from 'react'
export default function App() {
//声明状态
let [count, setCount] = useState(0);
let [sex, setSex] = useState(true);
//调用
//第一个参数为函数,箭头函数或匿名函数均可
//这个函数的作用等效于ComponentDidMount和ComponentDidUpdate
//这个方法可以执行多次
useEffect(function () {
console.log('我既执行ComponentDidMount效果,也执行ComponentDidUpdate效果-1');
});
useEffect(function () {
console.log('我既执行ComponentDidMount效果,也执行ComponentDidUpdate效果-2');
});
//当如果只想要ComponentDidMount效果,则需要给useEffect方法传入第二个参数,为数组
//数组的参数如果没有任何元素的话,表示不更新状态
/* useEffect(() => {
console.log('我只执行ComponentDidMount效果');
}, []) */
//如果想要某一个状态数据改变的时候,需要执行,则将这个状态数据添加即可
/* useEffect(() => {
console.log('我只执行ComponentDidMount效果');
}, [sex]) */
//如果想要模拟ComponentwillUnmount
//在脚手架中当尝试修改组件之后,react会将组件先卸载,然后再来一个新的重新挂载
useEffect(() => {
return () => {
console.log('组件将要被卸载时...')
}
})
let changeAdd = (num) => {
return () => {
count = count + num;
setCount(count);
}
}
let changeSex = () => {
setSex(!sex);
}
return (
<div>
<ul>
<li>
<p>数值:{count}</p>
<button onClick={changeAdd(10)}>新增</button>
</li>
</ul>
<ul>
<li>
<p>性别:{sex ? '男' : '女'}</p>
<button onClick={changeSex}>修改</button>
</li>
</ul>
</div>
)
}
但是使用useEffect()方法的时候有一个很重要的细节
,那就是useEffect方法的第一个函数不能是一个异步函数
也就是说,不能出现async
,原因是如果设置了async,返回值就成了promise对象,非一个函数了。
如果想要在useEffect()方法中添加异步代码,需要在函数中在单独声明一个函数
//如果想要模拟ComponentwillUnmount
//在脚手架中当尝试修改组件之后,react会将组件先卸载,然后再来一个新的重新挂载
useEffect(() => {
return () => {
async function main() {
console.log('在这里可以执行异步代码逻辑...');
}
main();
}
})
使用Hook 注意:
只在最顶层使用 Hook,不要在条件或循环中
只在React组件函数内部中调用 Hook, 不要在组件函数外部调用