React 是一个用于构建用户界面的 JavaScript 库。
如果从 MVC 的角度来看,React 仅仅是视图层(V),也就是只负责视图的渲染,而并非提供了 完整的 M 和 C 的功能。
React 起源于 Facebook ,并于 2013 年 5 月开源
<div id="root"></div>
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin>script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin>script>
// 创建元素(不是真正的dom元素,是一个react元素(虚拟dom))
const h1 = React.createElement("h1",null,"我是react创建出来的h1")
console.log(h1)
测试的时候,可以用.上线不能用
// console.dir(root)
将react元素,转成真正dom元素,然后渲染到页面上
ReactDOM.render(react元素/react组件, 渲染的位置)
ReactDOM.render(h1, document.getElementById('root'))
<script>
// 第二个参数为标签的属性
const h1 = React.createElement("h1", {
id: "rolls"
}, "我是react创建出来的h1")
<script>
// 第二个参数为标签的属性
const h1 = React.createElement("h1", {
id: "rolls",
className:"royce"
}, "我是react创建出来的h1")
const label = React.createElement("label",{
htmlFor:"test"
},"hello react")
const h1 =
注意: 浏览器并不认识jsx 所以需要引入babel将jsx编译成React.createElement的形式
编译 JSX 语法的包为:@babel/preset-react
<script src="https://unpkg.com/babel-standalone@6/babel.min.js">script>
<script type="text/babel">
const h1 = <h1 className="red">北京 上海 广州</h1>
ReactDOM.render(h1, document.getElementById("root"))
script>
表达式:简单的理解为:任何有返回值的一段代码
常见的表达式:字面量\变量\常量\数组\函数\对象\运算\三元
{}
<script type="text/babel">
// 元素内使用
const dv = <div className="abc"> JSX中使用表达式: { 1 + 2 } </div>
// 属性内使用
const dv = <div title={'我是标题'}>JSX中使用表达式: { 1 + 2 }</div>
// 直接使用JSX
const dv = <div>JSX中使用表达式: { <span>JSX自身也是合法的JS表达式</span> }</div>
ReactDOM.render(dv, document.getElementById("root"))
</script>
ReactDOM.render( ["北京","上海","广州"],document.getElementById("root"))
<script type="text/babel">
/*
未捕获的不变违反:对象作为React的子对象无效(找到:键为{name, age}的对象)。如果您打算呈现一组子元素,请使用数组。
*/
ReactDOM.render( {name:"rolls",age:99},document.getElementById("root"))
</script>
<script type="text/babel">
let data
let odd = 0;
if (odd) {
data = <h1>莱斯劳斯</h1>
}else{
data = <h1>宾利</h1>
}
ReactDOM.render(data,document.getElementById("root"))
</script>
<script type="text/babel">
let odd = 1;
let data = odd > 0? <h1>莱斯劳斯</h1> : <h1>宾利</h1>
ReactDOM.render(data,document.getElementById("root"))
</script>
<script type="text/babel">
// 如果num=7,展示所有数据 与运算属于短路运算,如果不满足则返回,第一个满足则直接返回第二个
let num
let arr = ["莱斯莱斯","宾利","法拉利","兰博基尼","布加迪"]
const data = <h1>{num =7 && arr}</h1>
ReactDOM.render(data,document.getElementById("root"))
</script>
<script type="text/babel">
let data = [{
name: "劳斯莱斯",
color: "black",
engine: "V12"
}, {
name: "宾利",
color: "white",
engine: "W12"
}]
const data_new = data.map((item,index) => {
return <li key={index}><div>车型:{item.name}</div><div>颜色:{item.color}</div><div>引擎:{item.engine}</div></li>
})
let ul = <ul>{data_new}</ul>
ReactDOM.render(ul,document.getElementById("root"))
</script>
如果没有key属性,也会显示,但是有警告信息
警告:列表中的每个孩子都应该有一个唯一的“键”道具。
<script type="text/babel">
let div = <div style={{color:"red",fontSize:50}}>北京 上海 广州</div>
ReactDOM.render(div,document.getElementById("root"))
</script>
let div = <div className="red50">北京 上海 广州</div>
ReactDOM.render(div,document.getElementById("root"))
React 元素的事件处理和 DOM 元素的很相似,但是有一点语法上的不同:
const div =
// 经测试,无论箭头函数还是function(){} this都是undefined
<script type="text/babel">
let btn = <button onClick={()=>{
console.log(this)
console.log("北上广不相信眼泪")
}}>点击我查看控制台</button>
ReactDOM.render(btn,document.getElementById("root"))
</script>
<script type="text/babel">
let link = <a href="" onClick={(event)=>{
console.log("a标签被点击")
event.persist()
// 阻值默认事件
event.preventDefault();
}}>点击我,会提交数据刷新页面</a>
ReactDOM.render(link,document.getElementById("root"))
</script>
React 中的事件对象叫做:合成事件 (兼容所有浏览器,无需担心跨浏览器兼容性问题)
**注意: **
function handleClick(e) {
e.preventDefault() //有效
console.log('事件对象', e)
// return false 无效
}
const div = (
<a href='https://www.baidu.com' onClick={handleClick}>
测试
</a>
)
const list = [
{ id: 1, name: 'jack', content: 'rose, you jump i jump' },
{ id: 2, name: 'rose', content: 'jack, you see you, one day day' },
{ id: 3, name: 'tom', content: 'jack,。。。。。' }
]
// 调用map方法,得到一个存储了一堆li标签的数组
const data = list.map((item)=>{
console.log(item);
return <li className="box" key={item.id} style={{backgroundColor:"pink"}}><a onClick={(event)=>{
event.preventDefault();
console.log(event.target.innerText);
}}><h3 style={{backgroundColor:"blue"}}>{item.name}</h3><p style={{backgroundColor:"yellow"}}>{item.content}</p></a></li>
})
let ul = <ul>{list.length ? data : <h1>暂无评论</h1>}</ul>
ReactDOM.render(ul,document.getElementById("root"));
组件允许你将 UI 拆分为独立可复用的代码片段,并对每个片段进行独立构思。
组件,从概念上类似于 JavaScript 函数。它接受任意的入参(即 “props”),并返回用于描述页面展示内容的 React 元素。
一个React应用就是由一个个的组件组成的
不能定义状态
export default function App(){
return(
<div>
我是一个根组件,所有组件通过我出去
</div>
)
}
// 函数名就是组件名
ReactDom.render(<App/>, document.getElementById('root'))
可以定义状态(状态: 组件内部私有的数据)
import React from "react"
export default class Module1 extends React.Component{
render(){
return(
<div>
<h1>我是最简单的类组件</h1>
</div>
)
}
}
// 如果组件没有子节点,也可以写成自闭和标签
ReactDom.render(<Module1 />, document.getElementById('root'))
import React from "react"
export default class Module2 extends React.Component{
constructor(){
// 否则无法使用this
super()
// this指向实例对象,state就是状态,固定写法
// 实例对象就相当于用的时候那个标签
this.state = {
count:7,
msg:"我是一个字符串"
}
}
render(){
// 在render里面也可通过this找到当前组件实例
console.log(this);
return(
<div>
<h3>{this.state.count}</h3> <p>{this.state.msg}</p>
</div>
)
}
}
状态(state)即数据
函数组件没有state,它this指向undefined 一般只负责渲染静态结构
类组件有自己的状态,负责更新 UI,让页面“动” 起来
状态(state)即数据,是组件内部的私有数据,只能在组件内部使用
state 的值是对象,表示一个组件中可以有多个数据
class Hello extends React.Component {
constructor() {
super()
this.state = { count: 0 } // 初始化state
}
// 简化语法
// state= { count: 0 }
render() {
return (
<div>{this.state.count}</div>
)
}
}
注意:不要直接修改 state 中的值,应该使用组件实例的setState方法,修改state的值
// 点击按钮 数字加一
export default class Module3 extends React.Component{
constructor(){
// 否则无法使用this
super()
// this指向实例对象,state就是状态,固定写法
// 实例对象就相当于用的时候那个标签
this.state = {
count:0,
msg:"点击按钮数字会增加哦003"
}
}
render(){
// 在render里面也可通过this找到当前组件实例
// console.log(this);
return(
<div>
<h3>{this.state.count}</h3> <p>{this.state.msg}</p>
<button onClick={()=>{
/*
this.state.count++ 数据会发生变化,但是视图(页面)不会变化
提供了实例对象一个方法 setState() 既可以改变state数据又可以更新视图
*/
this.setState({
// 在原来基础上运算的得到新的值再赋值 而不是++ -- 性能优化
count:this.state.count + 1
})
console.log(this.state.count);//简单理解为底层异步执行了,所以总是拿上一次的值
}}>按钮</button>
</div>
)
}
}
import React, { Component } from "react";
export default class App extends Component {
state = {
count: 0,
msg: "a",
};
handleCount = () => {
// 注意的第一个问题: setState多次连续调用.合并问题
this.setState({
count: this.state.count + 1,
msg: "b",
});
this.setState({
count: this.state.count + 2,
});
this.setState({
count: this.state.count + 3,
});
/*
合并之后效果其实是这样,并不是覆盖
this.setState({
count: this.state.count + 3,
msg : b
});
*/
console.log(this.state.count);
// 开始是0 延迟一次更新数据,异步
};
render() {
return (
<div>
<div>{this.state.count}</div>
<button onClick={this.handleCount}>点击我加数字</button>
</div>
);
}
}
import React, { Component } from "react";
export default class App extends Component {
state = {
count: 0,
msg: "a",
};
handleCount = () => {
this.setState(
// 这里接收到的state和props是最新的state和props
// 第一个参数除了可以像之前一样传入一个对象之外,还可以传入一个函数.如下
// 这个函数,接收一个最新的state和最新的props
// 函数要求返回一个对象
(state, props) => {
return {
count: state.count + 1,
};
},
() => {
console.log(this.state.count);
}
);
this.setState(
(state, props) => {
return {
count: state.count + 2,
};
},
() => {
console.log(this.state.count);
}
);
this.setState(
(state, props) => {
return {
count: state.count + 3,
};
},
() => {
console.log(this.state.count);
}
);
};
render() {
return (
<div>
<div>{this.state.count}</div>
<button onClick={this.handleCount}>点击我加数字</button>
</div>
);
}
}
// 最终状态中count的结果是 6
setState将对组件 state 的更改排入队列,所以调用之后,立刻获取this.state拿到的值很有可能是错误的
state = {count:0}
this.setState((state, props) => {
return {
count: state.count + 1
}
})
this.setState((state, props) => {
return {
count: state.count + 2
}
})
this.setState((state, props) => {
return {
count: state.count + 3
}
})
// 这里获取到的state中count的值就是错误的
console.log(this.state.count) // 0
import React, { Component } from "react";
export default class App extends Component {
state = {
count: 0,
msg: "a",
};
handleCount = () => {
this.setState({
count: this.state.count + 1
},()=>{
// 第二个参数:是数据修改完毕,并且视图更新完毕,才会调用
// 如果想要在setState之后立刻得到最新的state. 可以使用第二个可以选参数
console.log(this.state.count)
});
};
render() {
return (
<div>
<div>{this.state.count}</div>
<button onClick={this.handleCount}>点击我加数字</button>
</div>
);
}
}
为了提高代码的阅读性,最好把事件处理函数定义在结构的外面.
但是这样就带来了this指向的问题:
handle中的this 指向 undefined (原因: bable编译jsx. 采用的是严格模式, 普通函数this就指向undefined)
class Hello extends React.Component {
constructor() {
super()
this.state = { count: 0, num: 100 }// 初始化state
}
handle() {
//这里this指向undefined
this.setState({
count: this.state.count + 1
})
}
render() {
return <div onClick={this.handle}>{this.state.count}</div>
}
}
// 点击按钮 数字加一
export default class Module4 extends React.Component{
constructor(){
// 否则无法使用this
super()
// this指向实例对象,state就是状态,固定写法
// 实例对象就相当于用的时候那个标签
this.state = {
count:0,
msg:"点击按钮数字会增加哦004"
}
}
// 这个函数就是按钮的点击事件处理函数 是加在了原型身上
//触发点击事件的时候,react底层.最终在原型上找到了这个函数,然后普通调用了这个函数. 函数普通调用this应该执行window. 但是我们的代码,被babel编译过.变成了严格模式,所以this就指向了undefined
handleCount(){
// console.log("111"); 直接this.handleCount可以找到它,因为在原型上
// 要执行代码
// console.log(this); //undefined
this.setState({
count:this.state.count+1
})
}
render(){
// 在render里面也可通过this找到当前组件实例
// console.log(this);
return(
<div>
<h3>{this.state.count}</h3> <p>{this.state.msg}</p>
{/* */}
{/* 解决方式一 箭头函数 */}
<button onClick={()=>{
// 此时,handleCount就不是事件处理函数了
// 箭头函数才是事件处理函数 点击后会执行箭头函数里面代码
// 箭头函数没有this 这个this肯定指向当前组件实例
this.handleCount()
}}>按钮</button>
</div>
)
}
}
export default class Module1 extends React.Component{
constructor(){
super()
this.state = {
num:1,
msg:"点击我按钮数字增加",
}
// 解决方式2:bind 去原型上找到这个函数绑定给它的实例对象
this.handleCount = this.handleCount.bind(this)
}
// 实例对象上有这个方法
handleCount(){
this.setState({
num : this.state.num +1,
msg:"数据更新中"
})
}
render(){
return(
<div>
<h3>{this.state.num}</h3> <p>{this.state.msg}</p>
<button onClick={ this.handleCount }>按钮</button>
</div>
)
}
}
export default class Module1 extends React.Component{
// constructor(){
// super()
// this.state = {
// num:1,
// msg:"点击我按钮数字增加",
// }
// }
/*
ES7草案中提案.如果要给当前类实例添加属性,就不需要写constructor了,应该使用下面语法
*/
state = {
num : 1,
msg :"点击按钮数字增加"
}
/* 解决方式3: 类的实例
这样定义的函数.这个函数直接添加到了当前组件的实例身上
注意:虽然是ES7草案的语法,但是因为脚手架中使用了babel,所以可以放心使用
*/
handleCount = () => {
this.setState({
num : this.state.num +1,
msg:"数据更新中"
})
}
render(){
return(
<div>
<h3>{this.state.num}</h3> <p>{this.state.msg}</p>
{/* 底层是对象方法调用,所以这个函数里的this,一定指向当前组件实例 */}
<button onClick={ this.handleCount }>按钮</button>
</div>
)
}
}
组件是封闭的,要接收外部数据应该通过 props 来实现
props的作用:接收传递给组件的数据
给组件标签添加属性即可
import React, { Component } from 'react'
import Module1 from "./Module1"
export default class App extends Component {
state = {
outdata :"App根组件的数据"
}
render() {
return (
<div>
{/* 组件使用组件外部的数据,使用标签属性的方式传递 */}
<Module1
odata={this.state.outdata}
arr = {[1,3,5,7,6]}
obj = {{name:"rolls"}}
/>
</div>
)
}
}
函数组件通过参数props接收数据,类组件通过 this.props 接收数据
import React from 'react'
export default function Module1(props) {
console.log(props)
return (
<div>
<h1>{props.odata}</h1>
<div>{props.arr}</div>
</div>
)
}
import React from "react"
export default class Module1 extends React.Component{
render(){
// 使用props属性,来接收组件外部传进来的数据
console.log(this.props)
return(
<div>
<h3>{this.props.arr}</h3> <p>{this.props.odata}</p>
<div>{this.props.obj.name}</div>
</div>
)
}
}
class Hello extends React.Component {
constructor(props) {
// 推荐将props传递给父类构造函数
super(props)
}
render() {
return <div>接收到的数据:{this.props.age}</div>
}
}
对于组件来说,props 是外来的,无法保证组件使用者传入什么格式的数据
如果传入的数据格式不对,可能会导致组件内部报错
关键问题:组件的使用者不知道明确的错误原因
允许在创建组件的时候,就指定 props 的类型、格式等
作用:捕获使用组件时因为props导致的错误,给出明确的错误提示,增加组件的健壮性
实现方式:
//常见类型:array、bool、func、number、object、string还有element(react元素)
// 必填项:isRequired
// 特定结构的对象:shape({ })
import PropTypes from 'prop-types'
class Hello extends React.Component(){
static propTypes = {
msg: PropTypes.string.isRequired
list: PropTypes.array
obj: PropTypes.shape({
color: PropTypes.string,
fontSize: PropTypes.number
})
}
render(){
return <div msg="props校验" ></div>
}
}
export default class App extends Component {
state = {
outdata :"App根组件的数据"
}
render() {
return (
<div>
{/* 组件使用组件外部的数据,使用标签属性的方式传递 */}
<Module1
odata={this.state.outdata}
arr = {[1,3,5,7,6]}
obj = {{
color:"red",
fontSize:20
}}
str = {"string"}
num = {1}
// 刻意写错
age={""}
/>
</div>
)
}
}
props效验
import React, { Component } from 'react'
// 首先需要引入prop-types
import PropTypes from "prop-types"
export default class Module1 extends Component {
render() {
console.log(this.props)
return (
<div>
<h1>
{
this.props.arr.map((item,index)=>{
return <div key={index}>{item}</div>
})
}
</h1>
<div>{this.props.odata}</div>
</div>
)
}
}
// props效验的作用:就是传入props的时候,传入缺失了一些属性,或属性的值类型错误.可以报出更清晰的错误
Module1.propTypes = {
arr:PropTypes.array.isRequired,
str:PropTypes.string,
num:PropTypes.number.isRequired,
obj:PropTypes.shape({
color:PropTypes.string.isRequired,
fontSize:PropTypes.number
}).isRequired,
age:PropTypes.number
}
作用:给 props 设置默认值,在未传入 props 时生效
实现方式:
export default class App extends Component {
state = {
outdata :"App根组件的数据"
}
render() {
return (
<div>
{/* 组件使用组件外部的数据,使用标签属性的方式传递 */}
<Module1
odata={this.state.outdata}
arr = {[1,3,5,7,6]}
obj = {{
color:"red",
fontSize:20
}}
str = {"string"}
num = {1}
// 刻意不传
// age={""}
/>
</div>
)
}
}
props默认值
// props效验的作用:就是传入props的时候,传入缺失了一些属性,或属性的值类型错误.可以报出更清晰的错误
Module1.propTypes = {
arr:PropTypes.array.isRequired,
str:PropTypes.string,
num:PropTypes.number.isRequired,
obj:PropTypes.shape({
color:PropTypes.string.isRequired,
fontSize:PropTypes.number
}).isRequired,
age:PropTypes.number
}
// 给Module添加属性
// 不传用默认值,传了用传递的值
Module1.defaultProps = {
age :20
}
HTML 中的表单元素是可输入的,也就是有自己的可变状态.而React 中可变状态通常保存在 state 中,并且只能通过 setState() 方法来修改 .React将 state 与表单元素值value绑定到一起,由 state 的值来控制表单元素的值
受控组件:其值受到 React 控制的表单元素
组件中有表单项,表单项的值被组件的状态所控制
class Hello extends React.Component {
constructor() {
super()
this.state = { text: '' } // 初始化state
}
handle = e => {
//这里this指向当前组件实例
this.setState({
text: e.target.value
})
}
render() {
return <input onChange={this.handle} value={this.state.text}></input>
}
}
//补充: 文本框、文本域、下拉框 操作value属性 复选框 操作checked属性
import React from "react"
// 受控组件:组件中有表单项,表单项的值被组件的状态所控制
export default class Module extends React.Component{
state = {
val: "文本框默认值",
areaval: "文本域区域",
sele: "5",
checked:false,
radio:""
}
handleChange = (event)=>{
this.setState({
val : event.target.value,
})
}
handleChangeArea = (event) => {
this.setState({
areaval : event.target.value
})
}
handleChangesele = (event) =>{
this.setState({
sele : event.target.value
})
}
handleChangechecked = (event)=>{
this.setState({
checked :event.target.checked
})
}
handleChangeradio = (event)=>{
this.setState({
radio : event.target.value
})
}
render(){
return(
<div>
{/* 文本框 */}
<input
type="text"
value={this.state.val}
onChange={this.handleChange}
/>
{/* 文本域 */}
<textarea name="" id="" cols="30" rows="10"
value = {this.state.areaval}
onChange = {this.handleChangeArea}
></textarea>
{/* 下拉框 */}
<select name="" id=""
value={this.state.sele}
onChange={this.handleChangesele}
>
<option value="1">北京</option>
<option value="2">上海</option>
<option value="3">广州</option>
<option value="4">深圳</option>
<option value="5">西藏</option>
<option value="6">美国</option>
</select>
{/* 复选框 */}
<input type="checkbox"
checked = {this.state.checked}
onChange = {this.handleChangechecked}
/>
{/* 单选框 */}
<input type="radio"
name = "gender"
type = "radio"
value = {"女"}
onChange = {this.handleChangeradio}
/>
<input type="text"
name = "gender"
type = "radio"
value = {"男"}
onChange = {this.handleChangeradio}
/>
<div>{this.state.radio}</div>
</div>
)
}
}
柯里化(Currying),又称部分求值(Partial Evaluation),是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
import React from "react"
// 受控组件:组件中有表单项,表单项的值被组件的状态所控制
export default class Module extends React.Component{
state = {
val: "文本框默认值",
areaval: "文本域区域",
sele: "5",
checked:false,
radio:""
}
// 函数柯里化 共同使用的事件处理函数
handleChange = name => event => {
const result = name === "checked"? event.target.checked : event.target.value
this.setState({
[name] : result
})
}
/*
handleChange = (name) => {
return (event) => {
const result = name === "checked"? event.target.checked : event.target.value
this.setState({
[name] : result
})
}
}
*/
/*
fn = a => {
return b => {
return c => {
return d => {
}
}
}
}
fn = a => b => c => d => {
}
*/
render(){
return(
<div>
{/* 文本框 */}
<input
type="text"
value={this.state.val}
onChange={this.handleChange("val")}
/>
{/* 文本域 */}
<textarea name="" id="" cols="30" rows="10"
value = {this.state.areaval}
onChange = {this.handleChange("areaval")}
></textarea>
{/* 下拉框 */}
<select name="" id=""
value={this.state.sele}
onChange={this.handleChange("sele")}
>
<option value="1">北京</option>
<option value="2">上海</option>
<option value="3">广州</option>
<option value="4">深圳</option>
<option value="5">西藏</option>
<option value="6">美国</option>
</select>
{/* 复选框 */}
<input type="checkbox"
checked = {this.state.checked}
onChange = {this.handleChange("checked")}
/>
{/* 单选框 */}
<input type="radio"
name = "gender"
type = "radio"
value = {"女"}
onChange = {this.handleChange("radio")}
/>
<input type="text"
name = "gender"
type = "radio"
value = {"男"}
onChange = {this.handleChange("radio")}
/>
<div>{this.state.radio}</div>
</div>
)
}
}
借助于 ref,使用原生 DOM 方式来获取表单元素值
ref 的作用:获取 DOM 或组件
调用 React.createRef() 方法创建一个 ref 对象
constructor() {
super()
this.txtRef = React.createRef()
}
将创建好的 ref 对象添加到文本框中
<input type="text" ref={this.txtRef} />
通过 ref 对象获取到文本框的值
Console.log(this.txtRef.current.value)
生命周期图谱
constructor ==> render ==> componentDidMount
constructor: 创建组件时,最先执行 . 一般用于: 1. 初始化state 2. 为事件处理程序绑定this
render: 每次组件渲染都会触发 注意: 不能在render中调用setState()
componentDidMount: 组件挂载(完成DOM)渲染后 一般用于: 1. 发送网络请求 2. DOM操作
render ==> componentDidUpdate
setState() , forceUpdate(), 组件接收到新的props 都会导致更新
componentDidUpdate: 组件更新(完成DOM渲染)后
1 发送网络请求 2 DOM操作 注意:如果要setState() 必须放在一个if条件中
componentWillUnmount
**componentWillUnmount:**组件卸载(从页面中消失) 执行清理操作
import React, { Component } from "react";
// 注意:只有类组件才有生命周期钩子函数,函数组件没有
export default class Test extends Component {
constructor() {
super();
this.state = {
count: 0,
};
console.log("组件创建阶段 constructor");
}
render() {
console.log("render函数 -组件创建以及更新阶段都会调用");
return (
<div>
<div>我是Test组件,{this.state.count}</div>
<h2>我是Appp组件过来数据,{this.props.Appp}</h2>
<button
onClick={() => {
this.setState({
count: this.state.count + 1,
});
}}
>
点击按钮,更新Test组件状态 count加一
</button>
</div>
);
}
componentDidMount() {
console.log("组件创建阶段-componentDidMount-创建完成以后执行,只会执行一次");
}
componentDidUpdate(prevProps, prevState) {
console.log("更新阶段-componentDidUpdate", prevProps, prevState);
}
componentWillUnmount() {
console.log("卸载阶段-componentWillUnMount");
}
}
点击以后
第一个参数是传入的props 第二个参数是自己的状态 都是上一次的
卸载
import React, { Component } from 'react'
import Test from "./Test"
export default class App extends Component {
state ={
Appp:"这是根组件state数据",
num:123456
}
render() {
return (
<div>
{this.state.num && <Test Appp={this.state.Appp} num={this.state.num}/>}
<button onClick={()=>{
this.setState({
num:""
})
}}>App组件按钮,点击让Test卸载</button>
</div>
)
}
}
componentWillMount
componentWillUpdate,
componentWillReceiveProps
以上生命周期钩子函数在React v16.3后废弃
react组件通讯有三种方式.分别是props, context, pubsub
单向数据流 父传子
注意: 不管导入多少次. context.js里面的代码只执行一次.这样就可以断定其他文件中导入context对象和这个context是同一个
如果在每个页面创建React.reactContext那么不是同一个对象
import React from "react";
// 默认值是在不写provider的时候生效
const context = React.createContext("哈哈哈哈哈");
export default context;
import React, { Component } from "react";
import context from "./context";
// 引入父组件
import Far from "./page/Far";
export default class App extends Component {
state = {
msg: "我是App的state.msg",
};
render() {
return (
// 不管哪种方式,不写外面这个context.Provider就有默认值("哈哈哈哈")
<context.Provider value={this.state.msg}>
<div>
顶级--App组件 引入far<Far></Far>
</div>
</context.Provider>
);
}
}
context
Far
import React, { Component } from "react";
// 引入子组件
import Son from "./Son";
export default class Far extends Component {
render() {
return (
<div>
父组件 -- 我是Far组件,引入son组件
<Son></Son>
</div>
);
}
}
-使用Consumer组件接收数据 Son
import React, { Component } from "react";
import context from "../context";
export default class Son extends Component {
render() {
return (
<context.Consumer>
{(data) => <div>Son组件, {data}</div>}
</context.Consumer>
);
}
}
import React, { Component } from "react";
import context from "../context";
export default class Son extends Component {
// 给要使用Context的Son类,添加静态contextType属性,并赋值为context对象
// 那么Son的实例对象上context属性中就可以获取到需要的值
// 这是给当前组件添加静态属性的一种简写形式
static contextType = context;
render() {
console.log(this);
return <div>Son组件,{this.context}</div>;
}
}
Son.contextType = context;
**注意:**不要使用context随意传递数据,一般用于传递"全局"数据, 比如当前认证的用户、主题或首选语言
发布订阅机制
pubsubjs是一个用JavaScript编写的库。
利用订阅发布模式, 当一个组件的状态发生了变化,可以让其他多个组件更新这些变化.
实现:
在项目根目录下: npm install pubsub-js / yarn add pubsub-js
import PubSub from "pubsub-js" // 导入的PubSub是一个对象.提供了发布/订阅的功能
// 一个用于接收订阅信息的函数(接收外部传入的数据的函数)
// PubSub.subscribe() 用于订阅信息(相当于监听某个组件内部数据变化)
// TOPIC ==> 订阅话题.推荐使用常量
// 第二个参数: 用于接收数据的函数
// token 这一次订阅的令牌(用于取消订阅)
var token = PubSub.subscribe(TOPIC, function (msg, data) {
console.log( msg, data );
});
// 以异步的形式的发布一个话题
// TOPIC 通过这个话题,找到订阅这个话题的订阅者
// 'hello world!' 具体要传递的数据
PubSub.publish(TOPIC, 'hello world!');
// 发布的同步使用方法
// 慎用!!!! 因为会阻塞发布者的代码执行
PubSub.publishSync(TOPIC, 'hello world!');
// 取消指定的订阅
PubSub.unsubscribe(token);
// 取消某个话题的所有订阅
PubSub.unsubscribe(TOPIC);
// 取消所有订阅
PubSub.clearAllSubscriptions();
import React, { Component } from "react";
// 每个使用的页面都要引入pubsub
import PubSub from "pubsub-js";
import Far from "./page/Far";
export default class App extends Component {
componentDidMount() {
this.token = PubSub.subscribe("G63", function (msg, data) {
console.log("我是App订阅的消息:" + msg, data);
});
}
// 这个是只取消当前的订阅者
cancel = () => {
PubSub.unsubscribe(this.token);
};
render() {
return (
<div>
我是App组件
<Far></Far>
{/* 发布 */}
<button
onClick={() => {
PubSub.publish("rollsroyce", "N74B66 V12");
}}
>
App--发布信息--publish
</button>
{/* 取消 */}
<button onClick={this.cancel}>App--取消订阅G63话题</button>
</div>
);
}
}
import React, { Component } from "react";
// 每个使用的页面都要引入pubsub
import PubSub from "pubsub-js";
import Son from "./Son";
export default class Far extends Component {
// 一般组件都是在组件挂载成功之后订阅
// 当订阅的话题发布了之后,回调函数会被触发
componentDidMount() {
PubSub.subscribe("G63", function (msg, data) {
console.log("Far组件接收到的:" + msg, data);
});
}
render() {
return (
<div>
Far组件
<Son></Son>
{/* 这种方式所有订阅这个消息的人 全都取消订阅 */}
<button
onClick={() => {
PubSub.unsubscribe("G63");
}}
>
Far--取消所有G63话题
</button>
{/* 这个取消不管写在哪,所有的订阅全都取消 */}
<button
onClick={() => {
PubSub.clearAllSubscriptions();
}}
>
取消所有发布的订阅
</button>
</div>
);
}
}
import React, { Component } from "react";
// // 每个使用的页面都要引入pubsub
import PubSub from "pubsub-js";
export default class Son extends Component {
// Son发布信息函数
handle = () => {
PubSub.publish("G63", "梅赛德斯奔驰AMG");
};
// 订阅App消息
componentDidMount() {
PubSub.subscribe("rollsroyce", (msg, data) => {
console.log("我是Son订阅的消息:" + msg, data);
});
}
render() {
return (
<div>
Son组件,
<button onClick={this.handle}>Son--发布信息--G63</button>
</div>
);
}
}
react组件中只能有一个根组件.
之前使用div包裹的方式会给html结构增加很多无用的层级
为了解决这个问题,可以使用React.Fragment
function Hello(){
return (
// 渲染到页面之后,这个div就是一个多余的
<div>
<h1>fragment</h1>
<p>hello react</p>
</div>
)
}
// 将上面的写法,修改为下面的写法
function Hello(){
return (
// 这样就只会渲染h1和p
<React.Fragment>
<h1>fragment</h1>
<p>hello react</p>
</React.Fragment>
)
}
// 简写形式
function Hello(){
return (
// 这是React.Fragment的简写形式
<>
<h1>fragment</h1>
<p>hello react</p>
</>
)
}
减轻state 跟页面渲染无关的数据,不要放在state中(比如定时器的id.不要放在state中)
shouldComponentUpdate (减轻不必要的重新渲染)
**组件更新的机制: **父组件重新渲染时,也会重新渲染子组件
如何避免不必要的重新渲染呢?
import React, { Component } from "react";
import Son from "./Son"
export default class App extends Component {
state = {
count: 0,
};
handle = () => {
this.setState({
// count: this.state.count + 1
count: 0
});
};
render() {
console.log("App组件更新了")
return (
<>
<div>APP组件</div>
<h1>呵呵</h1>
<h2>哈哈哈</h2>
<Son count={this.state.count}></Son>
<button onClick={this.handle}>按钮,改变APP数据状态</button>
</>
);
}
}
import React, { Component } from "react";
export default class Son extends Component {
state = {
msg: "Sun旧的state.msg",
obj: "rolls",
};
// 这个函数是传入的新的props以及state更新的时候会被触发
// 这个函数要求返回一个boolean值,返回true,就执行render. 返回false就不执行
// shouldComponentUpdate 只有在更新阶段才会被调用
shouldComponentUpdate(nextProps, nextState) {
console.log("Son组件的 shouldComponentUpdate执行了");
// console.log(nextProps) 最新的props的值 {count: 1}
// console.log(this.props) 旧的props的值 {count: 1}
// console.log(nextState) 最新的state值 {msg: "Sun旧的state.msg", obj: "rolls"}
// console.log(this.state) 旧的state的值
if (
nextProps.count !== this.props.count ||
nextState.msg !== this.state.msg
) {
return true;
}
return false;
}
handleSon = () => {
this.setState({
msg: "Son新的state.msg",
});
};
render() {
console.log("Son组件更新了");
return (
<>
{/* 如果上面函数returnfalse 这个App传过来的值不会变 */}
<h1>Son组件,{this.props.count}</h1>
<button onClick={this.handleSon}>修改Son的状态数据</button>
</>
);
}
}
解决方式:使用钩子函数 shouldComponentUpdate(nextProps, nextState)
作用:通过返回值决定该组件是否重新渲染,返回 true 表示重新渲染,false 表示不重新渲染
触发时机:更新阶段的钩子函数,组件重新渲染前执行 (shouldComponentUpdate => render)
```javascript
// 父组件
class Far extends Component {
state = {
num: 0
}
// 返回min~max之间的随机整数(包含min和max)
getRandomIntInclusive(min, max) {
min = Math.ceil(min)
max = Math.floor(max)
return Math.floor(Math.random() * (max - min + 1)) + min //含最大值,含最小值
}
handle = () => {
let num = this.getRandomIntInclusive(1, 3)
this.setState({
num
})
}
//有效减少组件渲染次数
shouldComponentUpdate(props, state) {
// this.state 原来的state
// state 最新的state
return this.state.num !== state.num
}
render() {
console.log('父组件渲染了')
return (
Far组件
{this.state.num}
)
}
}
// 不管子组件有没有用到父组件的数据,只要父组件重新渲染了,子组件就会跟着渲染
// 子组件
class Son extends Component {
//有效减少组件渲染次数
shouldComponentUpdate(props, state) {
// this.props 原来的props
// props 最新的props
return props.num !== this.props.num
}
render() {
console.log('Son组件渲染了')
return (
Son组件
{this.props.num}
)
}
}
```
PureComponent 与 React.Component 功能相似,直接替换即可
区别:PureComponent 内部自动实现了 shouldComponentUpdate 钩子,不需要手动比较
原理:纯组件内部通过分别 对比 前后两次 props 和 state 的值,来决定是否重新渲染组件
**注意: **纯组件内部的对比是 shallow compare(浅层对比)
App
import React, { Component } from "react";
import Son from "./Son"
export default class App extends Component {
state = {
count: 0,
};
handle = () => {
this.setState({
// count: this.state.count + 1
count: 0
});
};
render() {
console.log("App组件更新了")
return (
<>
<div>APP组件</div>
<h1>呵呵</h1>
<h2>哈哈哈</h2>
<Son count={this.state.count}></Son>
<button onClick={this.handle}>按钮,改变APP数据状态</button>
</>
);
}
}
- Son
import React, { PureComponent } from "react";
export default class Son extends PureComponent {
state = {
msg: "Sun旧的state.msg",
obj: {
name : "rolls",
}
};
handleSon = () => {
let obj = { ...this.state.obj };
obj.name = "royce";
this.setState({
obj,
});
};
render() {
console.log("Son组件更新了");
return (
<>
{/* 如果上面函数returnfalse 这个App传过来的值不会变 */}
<h1>{this.state.obj.name}</h1>
<h1>Son组件,{this.props.count}</h1>
<button onClick={this.handleSon}>修改Son的状态数据</button>
</>
);
}
}
等待删除
```javascript
class Far extends PureComponent {
state = {
obj: {
num: 0
}
}
getRandomIntInclusive(min, max) {
min = Math.ceil(min)
max = Math.floor(max)
return Math.floor(Math.random() * (max - min + 1)) + min //含最大值,含最小值
}
handle = () => {
// 错误的写法
// 重新设置的obj和上一次的obj是同一个.
// 组件不会重新渲染了
// let num = this.getRandomIntInclusive(1, 3)
// let obj = this.state.obj 或者 let {obj} = this.state
// obj.num = num
// 正确的写法:
let num = this.getRandomIntInclusive(1, 3)
// 创建一个新的对象
let obj = { num }
console.log(num)
this.setState({
obj
})
}
render() {
console.log('父组件渲染了')
return (
Far组件
{this.state.obj.num}
)
}
}
```
函数组件不可以添加ref属性
作用: 将函数组件内部的元素,交给父组件使用
// 实现:
// App组件
const ref = React.createRef()
class App extends React.Component {
componentDidMount() {
// 在app组件中,通过ref获取到了button的Dom对象
console.log(ref.current)
}
render() {
return (
<div>
// 使用React.forwardRef包装后的FancyButton就可以添加ref属性了
<FancyButton ref={ref}>Click me!</FancyButton>
</div>
)
}
}
// FancyButton
const FancyButton = React.forwardRef((props, ref) => {
console.log(props, ref)
return (
<button ref={ref} className='FancyButton'>
{props.children}
</button>
)
})
// 你可以直接获取 DOM button 的 ref:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案
import React, { Component } from "react";
import Test from "./Test";
export default class App extends Component {
render() {
return (
<div>
<p>1</p>
{/* 在app组件中使用了portal组件
但是渲染dom的时候,portal的内容不在这个div中 */}
<Test />
<div>2</div>
</div>
);
}
}
import React, { Component } from "react";
import ReactDOM from "react-dom";
export default class Test extends Component {
constructor() {
super();
this.rollsroyce = document.createElement("div");
}
componentDidMount() {
document.body.appendChild(this.rollsroyce);
}
render() {
let node = (
<div>
<h1>Test组件</h1>
</div>
);
// 当前portal组件不直接返回元素,而是返回一个portal
// react底层会将node的内容,渲染到this.rollsroyce中
return ReactDOM.createPortal(node, this.rollsroyce);
}
}
**高阶组件的作用: ** 提供复用的状态逻辑
高阶组件是什么: 高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式
高阶组件的基本逻辑
开发者要去定义个函数:函数名一般以With开头
const 新的组件 = Withxxx(需要公用状态和逻辑的组件)
const Withxxx = 新组件(基础组件) 这个新的组件中有公共的状态和逻辑
后期长期使用新的组件
const EnhancedComponent = higherOrderComponent(WrappedComponent);
import React, { Component } from "react";
import Login from "./pages/Login";
import Register from "./pages/Register";
// 引入定义的高阶函数
import WithForm from "./WithForm";
// 调用高阶函数以后得到一个增强后的新组件
const WithLogin = WithForm(Login);
const WithRegister = WithForm(Register);
export default class App extends Component {
state = {
msg: "我是APP里面的数据",
};
render() {
return (
<div>
<h1>高阶组件</h1>
{/*
*/}
{/* 这些数据只是传递给高阶组件,将来还要再次传递给写的组件 */}
<WithLogin {...this.state} />
<WithRegister test={"数据最终会在register props上"} />
</div>
);
}
}
import React from "react";
// 注意:WithForm函数的形参,应该首字母大写,因为这个形参接收的是一个组件,后面要直接使用这个组件
export default function WithForm(WrappedComponent) {
return class extends React.Component {
/*
如果组件不写名字(匿名组件,可以这样),在react-dev-tool中默认展示_temp
如果组件设置了dispalyName静态属性,那么react-dev-tool中展示的组件名就是displayName的值
*/
static displayName = "With" + WrappedComponent.name;
state = {
username: "",
password: "",
repassword: "",
};
handleChange = (name) => (event) => {
this.setState({
[name]: event.target.value,
});
};
render() {
return (
<WrappedComponent
{...this.state}
/*
这个相当于
username={this.state.username}
password={this.state.password}
repassword={this.state.repassword}
*/
handleChange={this.handleChange}
// 如果不把WithLogin上传的props传递给login,login上是没有这些数据的
{...this.props}
></WrappedComponent>
);
}
};
}
import React, { Component } from "react";
export default class Login extends Component {
render() {
const { username, password, handleChange } = this.props;
return (
<div>
<div>{username}</div>
<div>{password}</div>
<h1>登录</h1>
<form>
用户名:
<input
type="text"
value={username}
onChange={handleChange("username")}
/>
<br />
密码:
<input
type="password"
value={password}
onChange={handleChange("password")}
/>
<br />
<input type="button" value="登录" />
</form>
</div>
);
}
}
import React, { Component } from "react";
export default class Register extends Component {
render() {
const { username, password, repassword, handleChange } = this.props;
return (
<div>
<div>{username}</div>
<div>{password}</div>
<div>{repassword}</div>
<h1>注册</h1>
<form>
用户名:
<input
type="text"
value={username}
onChange={handleChange("username")}
/>
<br />
密码:
<input
type="password"
value={password}
onChange={handleChange("password")}
/>
<br />
确认密码:
<input
type="password"
value={repassword}
onChange={handleChange("repassword")}
/>
<br />
<input type="button" value="登录" />
</form>
</div>
);
}
}
@higherOrderComponent
class WrappedComponent extends React.Component
**解决: **
给高阶组件中返回的组件, 增加一个静态属性displayName
static displayName = `XXX(${WrappedComponent.displayName ||WrappedComponent.name ||'Component'})`
//原理: 调试工具,优先展示组件的displayName
**render props的作用: **提供共享的状态逻辑
**render props是什么: **指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术
简单理解: 实现多个组件状态和逻辑共享的技术(复用)
export default class App extends Component {
render() {
return (
<div>
// 原来直接在这里渲染Cat和Mouse组件
// 使用了render props技术之后, 在这里使用封装了共享状态逻辑的组件,
// 真正要渲染的Cat和Mouse需要当做render这个prop的返回值传进去
<Position render={pos => <Cat {...pos} />}></Position>
<Position render={pos => <Mouse {...pos} />}></Position>
</div>
)
}
}
import { Component } from "react";
/*
利用render props技术定义的一个组件
render props技术需要开发者定义一个组件,使用这个组件
*/
export default class Position extends Component {
// 公用的状态
state = {
x: 0,
y: 0,
};
handleMove = (event) => {
// 获取到鼠标的坐标,然后赋值给state (根据需求逻辑定义共享逻辑代码)
this.setState({
x: event.clientX,
y: event.clientY,
});
};
componentDidMount() {
// 组件挂载成功,注册事件,监听鼠标移动
window.addEventListener("mousemove", this.handleMove);
}
componentWillUnmount() {
// 组件卸载的时候,要移除事件
window.removeEventListener("mousemove", this.handleMove);
}
render() {
/*
this.props.render拿到的就是一个回调函数
将鼠标的坐标,当做实参传入到render函数的位置上
this.props.render函数调用完毕之后,可以获取到对应想要渲染的组件
*/
return this.props.render(this.state);
}
}
import React, { Component } from "react";
import Cat from "./components/Cat";
import Mouse from "./components/Mouse";
import Position from "./Position";
export default class App extends Component {
render() {
return (
<div>
<h1>猫抓老鼠</h1>
{/*
*/}
{/* renderprops技术, 要求在使用Position组件的时候加一个render属性,render属性的值必须是一个回调函数*/}
{/* 这里使用的是postion组件,这个组件返回render,回调函数返回mouse组件,实际上回调函数返回谁,这个位置最终渲染谁的结构 */}
<Position render={(state) => <Mouse xxx={state}></Mouse>}></Position>
<Position render={(state) => <Cat xxx={state}></Cat>}></Position>
</div>
);
}
}
import React, { Component } from "react";
// react脚手架,不可以直接写图片的相对路径,需要导入一下
import MouseUrl from "../assets/mouse.gif";
export default class Mouse extends Component {
render() {
let { x, y } = this.props.xxx;
return (
<div>
<img
src={MouseUrl}
alt=""
style={{ position: "absolute", left: x, top: y, width: 100 }}
/>
</div>
);
}
}
import React, { Component } from "react";
// react脚手架,不可以直接写图片的相对路径,需要导入一下
import CatUrl from "../assets/cat.gif";
export default class Cat extends Component {
render() {
let { x, y } = this.props.xxx;
return (
<div>
<img
src={CatUrl}
alt=""
style={{ position: "absolute", left: x, top: y }}
/>
</div>
);
}
}
import { Component } from "react";
/*
利用render props技术定义的一个组件
render props技术需要开发者定义一个组件,使用这个组件
*/
export default class Position extends Component {
// 公用的状态
state = {
x: 0,
y: 0,
};
handleMove = (event) => {
// 获取到鼠标的坐标,然后赋值给state (根据需求逻辑定义共享逻辑代码)
this.setState({
x: event.clientX,
y: event.clientY,
});
};
componentDidMount() {
// 组件挂载成功,注册事件,监听鼠标移动
window.addEventListener("mousemove", this.handleMove);
}
componentWillUnmount() {
// 组件卸载的时候,要移除事件
window.removeEventListener("mousemove", this.handleMove);
}
render() {
/*
this.props.children拿到的就是一个回调函数 标签内的回调函数,给这个函数传参数
特别注意:
如果 {(state) => }
Position里面没有空格 name直接获取的是state这个回调函数
如果Position标签里面的回调和Position有空格,获取的是一个数组,包括空白文档,必须加下标1拿到的才是这个回调
*/
return this.props.children(this.state);
}
}
import React, { Component } from "react";
import Cat from "./components/Cat";
import Mouse from "./components/Mouse";
import Position from "./Position";
export default class App extends Component {
render() {
return (
<div>
<h1>猫抓老鼠</h1>
{/*
*/}
{/* renderprops技术, 要求在使用Position组件的时候加一个render属性,render属性的值必须是一个回调函数*/}
{/* 这里使用的是postion组件,这个组件返回render,回调函数返回mouse组件,实际上回调函数返回谁,这个位置最终渲染谁的结构 */}
{/* 第二种方式直接把这个回调函数写在position标签内部 */}
{/* 方式一 */}
<Position>{(state) => <Mouse xxx={state}></Mouse>}</Position>
{/* 建议使用方式二,因为你不知道他会传入多少个参数 */}
<Position>{(state) => <Cat {...state}></Cat>}</Position>
{/* 千万注意不要有空格,否则this.props.children获取到的是一个数组 */}
{/* {(state) => } */}
</div>
);
}
}
import React, { Component } from "react";
// react脚手架,不可以直接写图片的相对路径,需要导入一下
import CatUrl from "../assets/cat.gif";
export default class Cat extends Component {
render() {
// 方式二的接收方式
let { x, y } = this.props;
return (
<div>
<img
src={CatUrl}
alt=""
style={{ position: "absolute", left: x, top: y }}
/>
</div>
);
}
}
import React, { Component } from "react";
// react脚手架,不可以直接写图片的相对路径,需要导入一下
import MouseUrl from "../assets/mouse.gif";
export default class Mouse extends Component {
render() {
// 方式一的接收方式
let { x, y } = this.props.xxx;
return (
<div>
<img
src={MouseUrl}
alt=""
style={{ position: "absolute", left: x, top: y, width: 100 }}
/>
</div>
);
}
}
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性
Hook也叫钩子,本质就是函数,能让你使用 React 组件的状态和生命周期函数…
Hook 还没有完全替代class,但是未来会逐步替代
组件复用状态逻辑比较难 (HOC和render props 写起来比较麻烦),Hook 使你在无需修改组件结构的情况下复用状态逻辑
解决了this难以理解的问题
希望可以逐渐取代class
import React, { useState } from "react";
export default function Add() {
/*
useState可以在函数组件中使用状态
返回一个数组,数组中可以解构一个数据和一个操作数据的方法
setCount是用来操作count的方法
useState的值表示count的初始化值
在后续的重新渲染中 useState返回的第一个值将始终是更新后最新的state
*/
const [count, setCount] = useState(0);
const [num, setNum] = useState(100);
function handleCount() {
setCount(count + 1);
}
function handleNum() {
setNum(num - 1);
}
return (
<>
<div>{count}</div>
<div>{num}</div>
<button onClick={handleCount}>点击按钮count+1</button>
<button onClick={handleNum}>点击按钮Num减1</button>
</>
);
}
import React, { Component } from "react";
export default class Addplus extends Component {
state = {
count: 0,
num: 100,
};
handleCount = () => {
this.setState({
count: this.state.count + 1,
});
};
handleNum = () => {
this.setState({
num: this.state.num - 1,
});
};
render() {
return (
<>
<div>{this.state.count}</div>
<div>{this.state.num}</div>
<button onClick={this.handleCount}>点击按钮count+1</button>
<button onClick={this.handleNum}>点击按钮Num减1</button>
</>
);
}
}
可以在单个组件中多次使用
这个Hook,相当于componentDidMount, componentDidUpdate和componentWillUnmount的组合
第一个参数: 默认模拟的是组件挂载和组件更新
useEffect的作用: 就是为了开发者可以在函数组件中,使用生命周期钩子函数
import React, { useState, useEffect } from "react";
// 接收传递过来数据
export default function Add(props) {
const [count, setCount] = useState(0);
const [num, setNum] = useState(100);
/*
useEffect作用: 就是为了开发者可以在函数组件中使用声明周期钩子函数
生命周期钩子函数,这个回调函数相当于是类组件中生命周期钩子函数componentDidMount 和 componentDidUpData
*/
useEffect(() => {
console.log("模拟 componentDidMount 组件加载成功");
// 第一个参数(回调函数)里面可以再return一个函数
// return的这个函数模拟的是卸载的回调函数 componentWillUnmount
// 如果没写第二个参数,,,,它的机制就是每次先会卸载掉之前的组件,然后再全部重新渲染
return () => {
console.log("模拟 componentWillUnmount 组件卸载了");
};
// 如果在第二个参数的位置上传入一个空数组,则这个回调函数只表示componentDidMount
// 第二个参数里的值,只要监听的数据变化,就会变化 执行componentDidUpdata 一般很少在里面写
}, [props.msg, num]);
// 事件处理区域
function handleCount() {
setCount(count + 1);
}
function handleNum() {
setNum(num - 1);
}
return (
<>
<div>{count}</div>
<div>{num}</div>
<button onClick={handleCount}>点击按钮count+1</button>
<button onClick={handleNum}>点击按钮Num减1</button>
</>
);
}
useState
useEffect
useContext
useReducer
useCallback
useMemo
useRef
useImperativeHandle
useLayoutEffect
useDebugValue
// 一般自定义hooks的名字都是usexxx
import React, { useState, useEffect } from "react";
export default function usePosition() {
const [x, setX] = useState(0);
const [y, setY] = useState(0);
useEffect(() => {
// 创建的时候,注册鼠标移动事件
window.addEventListener("mousemove", handleMove);
return () => {
window.removeEventListener("mousemove", handleMove);
};
}, []);
// 注意: 这个函数写在useEffect前面是可以的,但是建议大家,写在所有的hooks后面,这样代码的刻度性更高
function handleMove(event) {
setX(event.clientX);
setY(event.clientY);
}
return { x, y };
}
import Cat from "./components/Cat";
import Mouse from "./components/Mouse";
export default function App() {
return (
<div>
<h1>猫抓老鼠</h1>
<Mouse></Mouse>
<Cat></Cat>
</div>
);
}
import CatUrl from "../assets/cat.gif";
import usePosition from "../myhooks";
export default function Cat() {
const { x, y } = usePosition();
return (
<div>
<img
src={CatUrl}
alt=""
style={{ position: "absolute", left: x, top: y }}
/>
</div>
);
}
import MouseUrl from "../assets/mouse.gif";
import usePosition from "../myhooks";
export default function Mouse() {
const { x, y } = usePosition();
return (
<div>
<img
src={MouseUrl}
alt=""
style={{ position: "absolute", left: x, top: y, width: 100 }}
/>
</div>
);
}