Appearance
react 基础-常用 Hook 和 API
Hook —— 在组件中使用不同的 React 特性。 内置 Hook 都是在函数顶层调用
useState
作用:保存组件数据并触发更新界面 用法:const [state,setState] = React.useState(xx); setState(newState) 或者 setState(state=>newState)
useRef
作用:保存组件数据、指向真实 DOM 或者组件实例,更改不会触发更新界面 用法:const myRef = React.useRef(xx);myRef.current 可读写
useEffect
作用:组件渲染完成后执行交互,如浏览器 API、以及网络请求数据等 用法:
js
React.useEffect(() => {
//在此执行副作用操作
// 每次渲染后都会执行此处的代码
//...
return () => {
//返回的函数是组件卸载的时候执行的
console.log("unmount--will call");
};
}); //没有传第二个参数,每次渲染都会执行第一个函数的代码
React.useEffect(() => {
//在此执行副作用操作
return () => {
//返回的函数是组件卸载的时候执行的,但如果监控了某个状态值,每次更新也会调用
console.log("unmount--will call");
};
}, [xx]);
//第二个参数,如果指定的是[],回调函数只有在第一次 render 的时候执行
//第二个参数有值(依赖),就表示这里面的值变化了之后,副作用也会执行,这是 react 专门设置的,为了更新的时候先清除数据再渲染
//跟 useState 一样,useEffect 也可以调用多次,可以在不同的 useEffect 里面实现不同的副作用,达到关注点分离编写 Effect 需要遵循以下三个规则:
- 声明 Effect。默认情况下,Effect 会在每次渲染后都会执行。
- 指定 Effect 依赖。大多数 Effect 应该按需执行,而不是在每次渲染后都执行。
- 必要时添加清理(cleanup)函数。
forwardRef(API)
作用:将函数组件内部的组件或者是 DOM 节点暴露给父组件。
- 一个函数组件要传 ref,必须将组件定义包装在 forwardRef() 中。
- 默认情况下,每个组件的 DOM 节点都是私有的。然而,有时候将 DOM 节点公开给父组件是很有用的,比如允许对它进行聚焦。 用法:
js
const MyInput = React.forwardRef(function MyInput(props, ref) {
return (
<label>
{props.label}
<input ref={ref} />
</label>
);
});useImperativeHandle
作用:forwardRef 会暴露真实的 DOM 节点,借助 useImperativeHandle 可以实现自定义暴露,只暴露提供自定义的方法集 用法: useImperativeHandle(ref, createHandle, dependencies?) 在组件顶层通过调用 useImperativeHandle 来自定义 ref 暴露出来的句柄:
js
const MyInput = React.forwardRef(function MyInput(props, ref) {
React.useImperativeHandle(
ref,
() => {
return {
// ... 你的方法 ...
};
},
[]
);
});useCallback
对于函数组件里面的回调函数,在组件重新渲染时,都会去重新创建执行 使用 useCallback 可以记忆函数,不会造成每次都重新创建,只有在对应的依赖更新时才会调用,起到缓存作用。 结合 memo API 的使用,可以允许你的组件在 props 没有改变的情况下跳过重新渲染。
js
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
// 不会执行第一个参数函数,只是直接返回,来达到存储函数的目的
// 常用来记忆事件函数,生成记忆后的时间函数并传递给子组件使用memo(API)
memo 允许你的组件在 props 没有改变的情况下跳过重新渲染。 memo(Component, arePropsEqual?)
js
function SomeComponent(){
xxxx
}
const MemoizedComponent = memo(SomeComponent, arePropsEqual?)
const MemoizedComponent = memo(SomeComponent, (prevProps, nextProps) => {
// 返回true表示相同不重新渲染
return (
prevProps.xxx === nextProps.xxx
);
})useMemo
使用 useMemo 在每次重新渲染的时候能够缓存计算的结果。
js
const cachedValue = useMemo(() => {
//经过一系列计算后的结果
}, dependencies);useCallback(fn, deps) 相当于 useMemo(() => fn, deps) 唯一区别:useMemo 会执行第一个参数函数,并返回函数执行结果
类组件简单介绍
类组件写法
js
/* 1.定义类组件 */
class MyCompont extends React.Component {
render() {
const VDOM = <h1>Hello React</h1>;
console.log("VDOM", VDOM);
return VDOM;
}
}
const root = ReactDOM.createRoot(document.getElementById("root"));
/* 2.将类组件渲染到页面上 */
root.render(<MyCompont />);state 和事件处理
- 定义 state 一般直接简写为 state=
- 组件中 render 方法中的 this 是实例对象本身
- 因为组件中的方法都是给回调使用,而非实例对象使用的,所以 this 是 undefined,解决方法有: 1).强制绑定 this:通过 bind 来强制绑定 this 并返回新函数 2).自定义函数用函数表达式+箭头函数
- 状态数据不能直接修改,要使用 this.setState({})来修改,且传入的参数是合并的操作,而不是替换,但只限于合并对象的一级属性,再往下应该就是整个替换
完整写法
js
class Weather extends React.Component {
constructor(props) {
super(props);
this.state = { isHot: true, wind: "热风" };
// 解决changeHot的this指向问题
this.changeHot = this.changeHot.bind(this);
}
render() {
console.log("render中的this", this);
//添加点击事件
return (
<h2 onClick={this.changeHot}>
今天天气很{this.state.isHot ? "炎热" : "凉爽"},{this.state.wind}
</h2>
);
//如果这样子写,就是实例调用,不需要再改变changeHot的this了
// return <h2 onClick={()=>{this.changeHot()}}>今天天气很{this.state.isHot?"炎热":"凉爽"},{this.state.wind}</h2>;
}
changeHot() {
console.log("changeHot--this", this);
//changeHot是放在哪里的?--Weather的原型对象上,供实例使用
//如果通过给h2点击onclick事件调用,那就是回调函数调用,是直接window调用,而非实例对应
//类中的方法都是开启了严格模式,this是undefined
// 严重注意,状态的修改必须通过setState({})修改,且传入的参数是合并的操作,而不是替换
this.setState({ isHot: !this.state.isHot });
// console.log("changeHot中的this",this);
}
}简写
js
class Weather extends React.Component {
// 初始化状态
state = { isHot: true, wind: "热风" };
render() {
console.log("render中的this", this);
//添加点击事件
return (
<h2 onClick={this.changeHot}>
今天天气很{this.state.isHot ? "炎热" : "凉爽"},{this.state.wind}
</h2>
);
}
// 自定义方法,要用赋值语句+箭头函数的形式
changeHot = () => {
this.setState({ isHot: !this.state.isHot });
};
}ref
组件内的标签可以通过 ref 来标识自己 如果是在组件内的虚拟 DOM 加上 ref,ref 获取到的就是真实的 DOM 如果是在组件加上 ref,获取到的就是组件实例对象,可以通过这个实例对象调用组件内的方法
ref 有三种使用方式
1.字符串的ref,直接在标签内ref写上字符串,会自动保存在refs中,通过字符串名字引用。(这种方式以后可能会被废除)
<input ref="input1" type="text"/>
this.refs.input1获取
2.回调方式的ref
内联函数回调(在数据更新时会触发两次回调,一次传null是为了清空数据,一次才是真实的dom)
<input ref={(c) => {this.input1 = c}} type="text"/>
this.input1获取
类绑定函数回调(还要自定义函数,写法复杂)
<input ref={this.saveNode} type="text"/>
3.React.createRef()创建
myRef = React.createRef();//这个放在实例自身上
<input ref={this.myRef} type="text"/>
myRef.current获取
生命周期
组件对象从创建到死亡,会经历特定阶段;React 组件对象包含一系列钩子函数(生命周期回调函数),在特定的时刻调用;在特定的生命周期回调函数中做特定的工作 生命周期分为分为三个阶段:
- 初始化阶段:由 ReactDOM.render()触发--初次渲染 挂载的流程: constructor ==> render ==> componentDidMount componentDidMount 比较常用,一般在这个钩子函数中做一些初始化的事,比如说设置定时器、发送网络请求、订阅消息
- 更新阶段:组件内部 this.setState({})或者父组件重新 render 触发 更新的流程 setState ==> shouldComponentUpdate ==> componentWillUpdate ==> render ==> componentDidUpdate shouldComponentUpdate 的返回值是 boolean 值,只有为 true 才会继续往下走 不写 shouldComponentUpdate 钩子的话,默认返回是 true 强制更新流程(调用 this.forceUpdate())触发 forceUpdate ==> componentWillUpdate ==> render ==> componentDidUpdate
- 卸载组件: componentWillUnmount 比较常用,一般在这个钩子函数中做一些收尾的事情,比如说取消定时器、取消订阅消息
js
class Count extends React.Component {
constructor(props) {
console.log("Count--constructor");
super(props);
this.state = { sum: 0 };
}
// 组件即将挂载的钩子
componentWillMount() {
console.log("Count--componentWillMount");
}
//挂载完成后的钩子
componentDidMount() {
console.log("Count--componentDidMount");
}
//控制组件更新的阀门
shouldComponentUpdate() {
console.log("Count--shouldComponentUpdate");
return true;
// return false;//返回false
}
// 组件将要更新的钩子
componentWillUpdate() {
console.log("Count--componentWillUpdate");
}
// 组件更新完成的钩子
componentDidUpdate() {
console.log("Count--componentDidUpdate");
}
//组件即将卸载的钩子
componentWillUnmount() {
console.log("Count--componentWillUnmount");
clearInterval(this.timer);
}
//加1的回调
add = () => {
console.log("Count--add");
this.setState({ sum: this.state.sum + 1 });
};
//强制更新按钮的回调
force = (event) => {
console.log("Count--force");
// console.log(event,event.target);
this.forceUpdate();
};
//卸载按钮的回调
unmount = () => {
console.log("Count--death");
ReactDOM.unmountComponentAtNode(document.getElementById("test"));
};
// 初始化渲染、数据更新
render() {
console.log("Count--render");
return (
<div>
<h2>当前求和为:{this.state.sum}</h2>
<button onClick={this.add}>点我+1</button>
<button onClick={this.unmount}>卸载组件</button>
<button onClick={this.force}>不setState,强制更新</button>
</div>
);
}
}作业
html
<!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>作业</title>
<style>
@charset "utf-8";
/* 样式重置 */
h1,
h2,
h3,
h4,
h5,
h6,
header,
hgroup,
hr,
input,
li,
ol,
p,
pre,
td,
textarea,
th,
ul {
margin: 0;
padding: 0;
}
ul,
oll,
li {
list-style: none;
}
a {
text-decoration: none;
color: #333;
}
i,
em {
font-style: normal;
}
input,
textarea,
button,
select,
a {
outline: none;
border: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
.todo-container {
position: static;
}
.todo-wrap {
width: 520px;
margin: 20px auto;
border: 1px solid #999;
border-radius: 5px;
}
.todo-main {
margin: 10px;
border: 1px solid #999;
border-bottom-width: 0;
width: 500px;
}
.btn {
padding: 5px 12px;
text-align: center;
font-size: 16px;
border-radius: 4px;
}
</style>
<script>
function onkeyDown() {
const keycode = window.event.keyCode;
if (keycode === 13) {
alert("按下了回车按键 ");
}
}
</script>
</head>
<body>
<div class="todo-wrap">
第15讲作业说明:<br />
1.提供一个Clock的函数组件,实现一个计时器的功能,对外提供的三个接口:start、stop、clear,分别对应开始计时、停止计时、和复位。<br />
2.Clock组件的界面:一个启动或者是停止的按钮,启动的时候显示停止,停止的时候显示启动,一个复位按钮,一个显示点击启动计时后经历过的时间。<br />
3.父组件上有一个计算器,默认值是1,一个加按钮点击加1,一个减按钮点击减1;下面是两个Clock子组件(计时器1、计时器2)<br />
4.当计算器值为1,5,9,13……时计算器1自动启动,为2,6,10,14……时计时器1自动停止,为3,7,11,15……时计时器2自动启动,为4,8,12,16……时计时器2自动停止<br />
</div>
<div class="todo-container">
<div class="todo-wrap">
<h3>父组件</h3>
<div style="text-align: center">
<button class="btn">-</button>
<span> 当前计数:1 </span>
<button class="btn">+</button>
</div>
<div style="background: #999; height: 1px"></div>
<div style="display: flex; flex-direction: row">
<div style="flex: 1; padding: 10px">
<h3>计时器1:</h3>
<div style="text-align: center">00:00:00</div>
<div
style="
margin: 0 10px;
display: flex;
flex-direction: row;
justify-content: space-between;
"
>
<button>启动/停止</button>
<button>复位</button>
</div>
</div>
<div style="background: #999; width: 1px"></div>
<div style="flex: 1; padding: 10px">
<h3>计时器1:</h3>
<div style="text-align: center">00:00:00</div>
<div
style="
margin: 0 10px;
display: flex;
flex-direction: row;
justify-content: space-between;
"
>
<button>启动/停止</button>
<button>复位</button>
</div>
</div>
</div>
</div>
</div>
</body>
</html>