React入门
创建项目
- 创建项⽬目: npx create-react-app my-app
- 打开项⽬目: cd my-app
- 启动项⽬目: npm start
- 暴暴露露配置项: npm run eject
CRA文件目录
├── README.md ⽂文档
├── public 静态资源
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
└── src 源码
├── App.css
├── App.js 根组件
├── App.test.js
├── index.css 全局样式
├── index.js ⼊入⼝口⽂文件
├── logo.svg
└── serviceWorker.js pwa⽀支持
├── package.json npm 依赖
tree
webpack.config.js 是webpack配置⽂文件,开头的常量量声明可以看出cra能够⽀支持ts、 sass及css模块化。
React和ReactDom
xxxxxxxxxx
React负责逻辑控制,数据 -> VDOM
ReactDom渲染实际DOM, VDOM -> DOM
React使⽤用JSX来描述UI
babel-loader把JSX 编译成相应的 JS 对象, React.createElement再把这个JS对象构造成React需
要的虚拟dom。
//这里的import ReactDOM不能加{}
JSX语法
JSX是⼀一种JavaScript的语法扩展,其格式⽐比较像模版语⾔言,但事实上完全是在JavaScript内部实现的。 JSX可以很好地描述UI,能够有效提⾼高开发效率,体验JSX。
基本使用
xxxxxxxxxx
const name = "react study";
const jsx = <div>hello, {name}</div>;
函数
xxxxxxxxxx
const obj = {
fistName: "Harry",
lastName: "Potter"
};
function formatName(name) {
return name.fistName + " " + name.lastName;
}
const jsx = <div>{formatName(user)}</div>;
对象
xxxxxxxxxx
const statement=<h1>hello world</h1>;
const context=<div>{statement}</div>;
条件语句
xxxxxxxxxx
const show=true;
const greet=<div>我有一个家</div>;
const context=<div>{
show?greet:"不,你没有"
}
{show&&greet}</div>;
//这里两个方式的结果是一样的
数组
const array=[1,2,3,4,5];
const context=<div><ul>
{array.map(item=>{
return <li key={item}>{item}</li>
})}</ul></div>
//这里是diff算法挂载组件的,这里时候,所以同级的元素类型,有一个唯一个的key。
这里item=>();这里()是返回值,如果是{}则要自己写一个return
属性的使用
xxxxxxxxxx
import logo from "./logo.svg";
const jsx = (
<div>
{/* 属性:静态值⽤用双引号,动态值⽤用花括号; class、 for等要特殊处理理。 */}
<img src={logo} style={{ width: 100 }} className="img" />
</div>
);
const context=<div><span style={{fontSize:"10px"}}> hello world</span></div>;
模块化
xxxxxxxxxx
css模块化,创建index.module.css, index.js
或者npm install sass -D
import style from "./index.module.css";
<img className={style.logo} />
import style from "./index.module.scss";
<img className={style.logo} />
组件
组件,从概念上类似于 JavaScript 函数。它接受任意的⼊入参(即 “props”),并返回⽤用于描述⻚页⾯面展示内容的 React 元素。组件有两种形式: class组件和function组件。
class组件
class组件通常拥有状态和⽣生命周期, 继承于Component, 实现render⽅方法。⽤用class组件创建⼀一个 Clock:
xxxxxxxxxx
import React from 'react';
export default class Clock extends React.Component{
constructor(props){
super(props);
this.state={date:new Date()};
}
componentDidMount(){
this.timerID=setInterval(() => {
this.setState({date:new Date()});
}, 1000);//这里timerID要用this来传递,只用const是无法传递的
}
componentWillUnmount(){
clearInterval(this.timerID);
}
render(){
// const {date}=this.state;//这里是另外 一种写法
return <div>{this.state.date.toLocaleTimeString()}</div>
}
}
function组件
函数组件通常⽆无状态,仅关注内容展示,返回渲染结果即可。 从React16.8开始引⼊入了了hooks,函数组件也能够拥有状态。 ⽤用function组件创建⼀一个Clock:
xxxxxxxxxx
import React,{useState,useEffect} from "react";
export function Clock(props){
const [date,setDate]=useState(new Date());
useEffect(()=>{
const timer=setInterval(() => {
setDate(new Date());//这里没有this....就只是setDate与对象this又有不同
}, 1000);//这里都是函数式组件 ,timer是一个计时器函数
return ()=>{clearInterval(timer);};//等于这一个写法 ()=>clearInterval(timer) 写法//清理 也会有问题
//return的内容在组件unMount的时候使用
},[]);
return <div>{date.toLocaleTimeString()}</div>
}
正确使用setState
正确使⽤用setState setState(partialState, callback)
- partialState : object|function ⽤用于产⽣生与当前state合并的⼦子集。
- callback : function state更更新之后被调⽤用。
关于 setState() 你应该了了解三件事:
不不要直接修改 State
错误代码
xxxxxxxxxx
// 错误示范
this.state.comment = 'Hello';
而是用setState来改变
xxxxxxxxxx
// 正确使⽤用
this.setState({comment: 'Hello'});
State 的更更新可能是异步的
出于性能考虑, React 可能会把多个 setState() 调⽤用合并成⼀一个调⽤用。 观察以下例例⼦子中log的值和button显示的counter。
xxxxxxxxxx
import React,{Component} from 'react';
export default class Counter extends Component{
constructor(props){
super(props);
this.state={value:0};
}
changeValue= v=>{
this.setState({value:this.state.value+v});
}
setCounter=()=>{
this.changeValue(1);
};
render(){
const {value}=this.state;
return (
<div>
<h1>SetStatePage</h1>
<button onClick={this.setCounter}>{value}</button>
</div>
);
}
}
获取最新的state值的方法
-
在回调中获取状态值
xxxxxxxxxx
changeValue = v => {
this.setState(
{
counter: this.state.counter + v
},
() => {
console.log("counter", this.state.counter);
}
);
};
-
使⽤用定时器器:
xxxxxxxxxx
setTimeout(() => {
this.setCounter();
}, 0);
-
-
原⽣生事件中修改状态
xxxxxxxxxx
componentDidMount(){
document.body.addEventListener('click', this.changeValue, false)
}
总结: setState只有在合成事件和⽣生命周期函数中是异步的,在原⽣生事件和setTimeout中都是同步 的,这⾥里里的异步其实是批量量更更新。
State 的更更新会被合并
xxxxxxxxxx
changeValue = v => {
this.setState({
counter: this.state.counter + v
});
};
setCounter = () => {
this.changeValue(1);
this.changeValue(2);
};
如果想要链式更更新state:
xxxxxxxxxx
changeValue = v => {
this.setState(state => ({ counter: state.counter + v }));
};
setCounter = () => {
this.changeValue(1);
this.changeValue(2);
};
生命周期
生命周期方法
⽣生命周期⽅方法,⽤用于在组件不不同阶段执⾏行行⾃自定义功能。在组件被创建并插⼊入到 DOM 时(即挂载中阶段(mounting) ),组件更更新时,组件取消挂载或从 DOM 中删除时,都有可以使⽤用的⽣生命周期⽅方法。
React V16.3之前的生命周期
V16.4之后的⽣生命周期:
V17可能会废弃的三个⽣生命周期函数⽤用getDerivedStateFromProps替代,⽬目前使⽤用的话加上 UNSAFE_: componentWillMount componentWillReceiveProps componentWillUpdate 引⼊入两个新的⽣生命周期函数: static getDerivedStateFromProps getSnapshotBeforeUpdate
如果不不想⼿手动给将要废弃的⽣生命周期添加 UNSAFE_ 前缀,可以⽤用下⾯面的命令。
xxxxxxxxxx
npx react-codemod rename-unsafe-lifecycles <path>
新引入的两个生命周期函数
getDerivedStateFromProps
xxxxxxxxxx
static getDerivedStateFromProps(props, state)
getDerivedStateFromProps 会在调⽤用 render ⽅方法之前调⽤用,并且在初始挂载及后续更更新时都会被 调⽤用。它应返回⼀一个对象来更更新 state,如果返回 null 则不不更更新任何内容。
请注意,不不管原因是什什么,都会在每次渲染前触发此⽅方法。这与 UNSAFE_componentWillReceiveProps 形成对⽐比,后者仅在⽗父组件重新渲染时触发,⽽而不不是在内部 调⽤用 setState 时。
getSnapshotBeforeUpdate
xxxxxxxxxx
getSnapshotBeforeUpdate(prevProps, prevState)
在render之后,在componentDidUpdate之前。 getSnapshotBeforeUpdate() 在最近⼀一次渲染输出(提交到 DOM 节点)之前调⽤用。它使得组件能 在发⽣生更更改之前从 DOM 中捕获⼀一些信息(例例如,滚动位置)。此⽣生命周期的任何返回值将作为参数传 递给 componentDidUpdate(prevProps, prevState, snapshot) 。
验证生命周期
范例例:创建LifeCyclePage.js
xxxxxxxxxx
import React, { Component } from "react";
import PropTypes from "prop-types";
/*
V17可能会废弃的三个⽣生命周期函数⽤用getDerivedStateFromProps替代,⽬目前使⽤用的话加上
UNSAFE_:
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
*/
export default class LifeCyclePage extends Component {
static defaultProps = {
msg: "omg"
};
static propTypes = {
msg: PropTypes.string.isRequired
};
constructor(props) {
super(props);
this.state = {
count: 0,
};
console.log("constructor", this.state.count);
}
static getDerivedStateFromProps(props, state) {
// getDerivedStateFromProps 会在调⽤用 render ⽅方法之前调⽤用,
//并且在初始挂载及后续更更新时都会被调⽤用。
//它应返回⼀一个对象来更更新 state,如果返回 null 则不不更更新任何内容。
const { count } = state;
console.log("getDerivedStateFromProps", count);
return count < 5 ? null : { count: 0 };
}
//在render之后,在componentDidUpdate之前。
getSnapshotBeforeUpdate(prevProps, prevState, snapshot) {
const { count } = prevState;
console.log("getSnapshotBeforeUpdate", count);
return null;
}
/* UNSAFE_componentWillMount() {
//不不推荐,将会被废弃
console.log("componentWillMount", this.state.count);
} */
componentDidMount() {
console.log("componentDidMount", this.state.count);
}
componentWillUnmount() {
//组件卸载之前
console.log("componentWillUnmount", this.state.count);
}
/* UNSAFE_componentWillUpdate() {
//不不推荐,将会被废弃
console.log("componentWillUpdate", this.state.count);
} */
componentDidUpdate() {
console.log("componentDidUpdate", this.state.count);
}
shouldComponentUpdate(nextProps, nextState) {
const { count } = nextState;
console.log("shouldComponentUpdate", count, nextState.count);
return count !== 3;
}
setCount = () => {
this.setState({
count: this.state.count + 1,
});
};
render() {
const { count } = this.state;
console.log("render", this.state);
return (
<div>
<h1>我是LifeCycle⻚页⾯面</h1>
<p>{count}</p>
<button onClick={this.setCount}>改变count</button>
{/* {!!(count % 2) && <Foo />} */}
<Child count={count} />
</div>
);
}
}
class Child extends Component {
UNSAFE_componentWillReceiveProps(nextProps) {
//不不推荐,将会被废弃
// UNSAFE_componentWillReceiveProps() 会在已挂载的组件接收新的 props 之前被调⽤用
console.log("Foo componentWillReceiveProps");
}
componentWillUnmount() {
//组件卸载之前
console.log(" Foo componentWillUnmount");
}
render() {
return (
<div
style={{ border: "solid 1px black", margin: "10px", padding: "10px" }}
>
我是Foo组件
<div>Foo count: {this.props.count}</div>
</div>
);
}
}
组件复合
组件复合 - Composition
复合组件给与你⾜足够的敏敏捷去定义⾃自定义组件的外观和⾏行行为,这种⽅方式更更明确和安全。如果组件间有公⽤用的⾮非UI逻辑,将它们抽取为JS模块导⼊入使⽤用⽽而不不是继承它。
不具名
xxxxxxxxxx
import React, { Component } from "react";
import TopBar from "../components/TopBar";
import BottomBar from "../components/BottomBar";
export default class Layout extends Component {
componentDidMount() {
const { title = "商城" } = this.props;
document.title = title;
}
render() {
const { children, showTopBar, showBottomBar } = this.props;
console.log("children", children);
return (
<div>
{showTopBar && <TopBar />}
{children.content}
{children.txt}
<button onClick={children.btnClick}>button</button>
{showBottomBar && <BottomBar />}
</div>
);
}
}
xxxxxxxxxx
import React, { Component } from "react";
import Layout from "./Layout";
export default class UserPage extends Component {
render() {
return (
<Layout showTopBar={true} showBottomBar={true} title="⽤用户中⼼心">
<div>
<h3>UserPage</h3>
</div>
</Layout>
);
}
}
具名
传个对象进去:
xxxxxxxxxx
import React, { Component } from "react";
import Layout from "./Layout";
export default class HomePage extends Component {
render() {
return (<Layout showTopBar={false} showBottomBar={true} title="商城⾸首⻚页">
{/* <div>
<h3>HomePage</h3>
</div> */}
{{
content: (
<div>
<h3>HomePage</h3>
</div>
),
txt: "这是个⽂文本",
btnClick: () => {
console.log("btnClick");
}
}}
</Layout>
);
}
}
实现⼀一个简单的复合组件,如antD的Card
xxxxxxxxxx
import React, { Component } from 'react'
function Card(props) {
return <div xu="card">
{
props.children
}
</div>
}
function Formbutton(props) {
return <div className="Formbutton">
<button onClick={props.children.defaultBtns.searchClick}>默认查询</button>
<button onClick={props.children.defaultBtns.resetClick}>默认重置</button>
{
props.children.btns.map((item, index) => {
return <button key={'btn' + index} onClick={item.onClick}>{item.title}
</button>
})
}
</div>
}
export default class CompositionPage extends Component {
render() {
return (
<div>
<Card>
<p>我是内容</p>
</Card>
CompositionPage
<Card>
<p>我是内容2</p>
</Card>
<Formbutton>
{{
/* btns: (
<>
<button onClick={() => console.log('enn')}>查询</button>
<button onClick={() => console.log('enn2')}>查询2</button>
</>
) */
defaultBtns: {
searchClick: () => console.log('默认查询'),
resetClick: () => console.log('默认重置')
},
btns: [
{
title: '查询',
onClick: () => console.log('查询')
}, {
title: '重置',
onClick: () => console.log('重置')
}
]
}}
</Formbutton>
</div>
)
}
}
redux
你可能不不需要redux
Redux 是负责组织 state 的⼯工具,但你也要考虑它是否适合你的情况。不不要因为有⼈人告诉你要⽤用 Redux 就去⽤用,花点时间好好想想使⽤用了了 Redux 会带来的好处或坏处。 在下⾯面的场景中,引⼊入 Redux 是⽐比较明智的:
你有着相当⼤大量量的、随时间变化的数据; 你的 state 需要有⼀一个单⼀一可靠数据来源; 你觉得把所有 state 放在最顶层组件中已经⽆无法满⾜足需要了了。 某个组件的状态需要共享。
redux
redux 是 JavaScript应⽤用的状态容器器,提供可预测化的状态管理理。它保证程序⾏行行为⼀一致性且易易于测试。
安装redux
⽤用⼀一个累加器器举例例
-
需要⼀一个store来存储数据
-
store⾥里里的reducer初始化state并定义state修改规则
-
通过dispatch⼀一个action来提交对数据的修改
-
action提交到reducer函数⾥里里,根据传⼊入的action的type,返回新的state
ReduxPage.js
xxxxxxxxxx
import React,{ Component } from "react";
import store from "./reduxStore";
export default class ReduxPage extends Component{
componentDidMount(){
store.subscribe(()=>{
this.forceUpdate();
})
}
add=()=>store.dispatch({type:"ADD"});
minus=()=>store.dispatch({type:"MINUS"});
render(){
return (
<div>ReduxPage
<p>{store.getState()}</p>
<button onClick={this.add}>add</button>
<button onClick={this.minus}>minus</button>
</div>
);
};
}
reduxStore.js
xxxxxxxxxx
import { createStore } from "redux";
const createReducer=(state=0,action)=>{
switch (action.type) {//这里的type匹配的是type
case "MINUS":
return state-1;
case "ADD":
return state+1;
default:
return state;
}
}
const store=createStore(createReducer);
export default store;
index.js
xxxxxxxxxx
const context=<div>
<ReduxPage /></div>;
const render = ()=>{
ReactDOM.render(
context,
document.querySelector('#root')
)
};
render();
store.subscribe(render);
检查点
安装
xxxxxxxxxx
npm install react-redux --save
使用react-redux
react-redux提供了了两个api
-
Provider 为后代组件提供store
-
connect 为组件提供数据和变更更⽅方法 全局提供store, index.js
./store/index.js
xxxxxxxxxx
import { createStore } from "redux";//这里记得导入redux
const createReducer=(state=0,action)=>{
switch (action.type) {//这里的type匹配的是type
case "MINUS":
return state-1;
case "ADD":
return state+1;
default:
return state;
}
}
const store=createStore(createReducer);
//用redux创建store
export default store;
ReduxPage.js
xxxxxxxxxx
import React,{Component} from 'react';
import {connect} from "react-redux";
class ReduxPage extends Component{
render(){
const {num,add,minus}=this.props;//从props中得入参数,store中取
return (
<div>
<h1>reduxPage</h1>
<h3>{num}</h3>
<button onClick={add}>add</button>
<button onClick={minus}>minus</button>
</div>
);
}
}
const mapStateToProps = state => {
return {
num:state,
}
}//派发state
const mapDispatchToProps ={
add:()=>{return {type:"ADD"};},
minus:()=>{return {type:"MINUS"}}
}//派发dispatch
//连接state和dispatch用来应对传入的store
export default connect(mapStateToProps, mapDispatchToProps)(ReduxPage);
react-router
react-router包含3个库, react-router、 react-router-dom和react-router-native。 react-router提供最 基本的路路由功能,实际使⽤用的时候我们不不会直接安装react-router,⽽而是根据应⽤用运⾏行行的环境选择安装 react-router-dom(在浏览器器中使⽤用)或react-router-native(在rn中使⽤用)。 react-router-dom和 react-router-native都依赖react-router,所以在安装时, react-router也会⾃自动安装,创建web应⽤用, 使⽤用:
安装
xxxxxxxxxx
npm install --save react-router-dom
基本使用
react-router中奉⾏行行⼀一切皆组件的思想,路路由器器-Router、链接-Link、路路由-Route、独占-Switch、重定向-Redirect都以组件形式存在
创建RouterPage.js
xxxxxxxxxx
import React,{Component} from 'react';
import {BrowserRouter as Router,Route,Link} from 'react-router-dom';
class HomePage extends Component{
render(){
return (
<div>
<h3>HomePage</h3>
</div>
);
}
}
class UserPage extends Component{
render(){
return (
<div>
<h3>UserPage</h3>
</div>
);
}
}
export default class RouterPage extends Component{
render(){
return (
<div>
<h3>RouterPage</h3>
<Router>
<Link to="/">首页</Link>
<Link to="/user"> 个人中心</Link>
<Route
exact
path="/" component={HomePage}></Route>
<Route path="/user" component={UserPage}/>
</Router>
</div>
);
}
}
Route渲染内容的三种方式
Route渲染优先级: children>component>render。 这三种⽅方式互斥,你只能⽤用⼀一种
children: func
有时候,不不管location是否匹配,你都需要渲染⼀一些内容,这时候你可以⽤用children。 除了了不不管location是否匹配都会被渲染之外,其它⼯工作⽅方法与render完全⼀一样。
render: func
但是当你⽤用render的时候,你调⽤用的只是个函数。 只在当location匹配的时候渲染
component: component
只在当location匹配的时候渲染。
404⻚面
设定⼀一个没有path的路路由在路路由列列表最后⾯面,表示⼀一定匹配
xxxxxxxxxx
{
/* 添加Switch表示仅匹配⼀一个*/}
<Switch>
{/* 根路路由要添加exact,实现精确匹配 */}
<Route
exact
path="/"
component={HomePage}
/>
<Route path="/user" component={UserPage} />
<Route component={EmptyPage} />
</Switch>
404
xxxxxxxxxx
class EmptyPage extends Component {
render() {
return (
<div>
<h3>EmptyPage-404</h3>
</div>
);
}
}
PureComponent
实现性能优化
定制了了shouldComponentUpdate后的Component
xxxxxxxxxx
import React, { Component, PureComponent } from "react";
export default class PureComponentPage extends PureComponent {
constructor(props) {
super(props);
this.state = {
counter: 0,
// obj: {
// num: 2,
// },
};
}
setCounter = () => {
this.setState({
counter: 100,
// obj: {
// num: 200,
// },
});
};
render() {
const { counter, obj } = this.state;
console.log("render");
return (
<div>
<h1>PuerComponentPage</h1>
<div onClick={this.setCounter}>counter: {counter}</div>
</div>
);
}
}
浅⽐较
缺点是必须要⽤用class形式,⽽而且要注意是浅比较
与Component`
React.PureComponent 与 React.Component 很相似。两者的区别在于 React.Component 并未实 现 shouldComponentUpdate() ,⽽而 React.PureComponent 中以浅层对⽐比 prop 和 state 的⽅方式来 实现了了该函数。 如果赋予 React 组件相同的 props 和 state, render() 函数会渲染相同的内容,那么在某些情况下使 ⽤用 React.PureComponent 可提高性能。
注意 React.PureComponent 中的 shouldComponentUpdate() 仅作对象的浅层⽐比较。如果对象中 包含复杂的数据结构,则有可能因为⽆无法检查深层的差别,产⽣生错误的⽐比对结果。仅在你的 props 和 state 较为简单时,才使⽤用 React.PureComponent ,或者在深层数据结构发⽣生变化时 调⽤用 forceUpdate() 来确保组件被正确地更更新。你也可以考虑使⽤用 immutable 对象加速嵌套
数据的⽐比较。 此外, React.PureComponent 中的 shouldComponentUpdate() 将跳过所有⼦子组件树的 prop 更更新。因此,请确保所有⼦子组件也都是“纯”的组件
认识Hook
认识Hook
Hook 是什什么? Hook 是⼀一个特殊的函数,它可以让你“钩⼊入” React 的特性。例例如, useState 是允许 你在 React 函数组件中添加 state 的 Hook。 什什么时候我会⽤用 Hook? 如果你在编写函数组件并意识到需要向其添加⼀一些 state,以前的做法是必须 将其它转化为 class。现在你可以在现有的函数组件中使⽤用 Hook。
xxxxxxxxxx
import React, { useState } from "react";
export default function HookPage(props) {
// 声明⼀一个叫 “count” 的 state 变量量,初始化为0
const [count, setCount] = useState(0);
return (
<div>
<h3>HookPage</h3>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>add</button>
</div>
);
}
使用 Effect Hook
Effect Hook 可以让你在函数组件中执⾏行行副作⽤用操作。 数据获取,设置订阅以及⼿手动更更改 React 组件中的 DOM 都属于副作⽤用。不不管你知不不知道这些操作,或是“副作⽤用”这个名字,应该都在组件中使⽤用过它们。
xxxxxxxxxx
import React, { useState, useEffect } from "react";
export default function HookPage(props) {
// 声明⼀一个叫 “count” 的 state 变量量,初始化为0
const [count, setCount] = useState(0);
// 与 componentDidMount 和 componentDidUpdate相似
useEffect(() => {
// 更更新 title
document.title = `You clicked ${count} times`;
});
return (
<div>
<h3>HookPage</h3>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>add</button>
</div>
);
}
在函数组件主体内(这⾥里里指在 React 渲染阶段)改变 DOM、添加订阅、设置定时器器、记录⽇日志以及执 ⾏行行其他包含副作⽤用的操作都是不不被允许的,因为这可能会产⽣生莫名其妙的 bug 并破坏 UI 的⼀一致性。 使⽤用 useEffect 完成副作⽤用操作。赋值给 useEffect 的函数会在组件渲染到屏幕之后执⾏行行。你可以 把 effect 看作从 React 的纯函数式世界通往命令式世界的逃生通道。
默认情况下, effect 将在每轮渲染结束后执⾏行行,但你可以选择让它 在只有某些值改变的时候 才执⾏行行。
effect 的条件执行
默认情况下, effect 会在每轮组件渲染完成后执⾏行行。这样的话,⼀一旦 effect 的依赖发⽣生变化,它就会被 重新创建。 然⽽而,在某些场景下这么做可能会矫枉过正。⽐比如,在上⼀一章节的订阅示例例中,我们不不需要在每次组件 更更新时都创建新的订阅,⽽而是仅需要在 source props 改变时重新创建。 要实现这⼀一点,可以给 useEffect 传递第⼆二个参数,它是 effect 所依赖的值数组。更更新后的示例例如 下:
xxxxxxxxxx
import React, { useState, useEffect } from "react";
export default function HookPage(props) {
// 声明⼀一个叫 “count” 的 state 变量量,初始化为0
const [count, setCount] = useState(0);
const [date, setDate] = useState(new Date());
// 与 componentDidMount 和 componentDidUpdate相似
useEffect(() => {
// 更更新 title
document.title = `You clicked ${count} times`;
}, [count]);
useEffect(() => {
const timer = setInterval(() => {
setDate(new Date());
}, 1000);
}, []);
return (
<div>
<h3>HookPage</h3>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>add</button>
<p>{date.toLocaleTimeString()}</p>
</div>
);
}
此时,只有当 useEffect第⼆二个参数数组⾥里里的数值 改变后才会重新创建订阅。
清除 effect
通常,组件卸载时需要清除 effect 创建的诸如订阅或计时器器 ID 等资源。要实现这⼀一点, useEffect 函数需返回⼀一个清除函数,以防⽌止内存泄漏漏,清除函数会在组件卸载前执⾏行行。
xxxxxxxxxx
useEffect(() => {
const timer = setInterval(() => {
setDate(new Date());
}, 1000);
return () => clearInterval(timer);
}, []);
自定义Hook与Hook使用规则
自定义Hook
有时候我们会想要在组件之间重⽤用⼀一些状态逻辑。⽬目前为⽌止,有两种主流⽅方案来解决这个问题: ⾼高阶组件和 render props。⾃自定义 Hook 可以让你在不不增加组件的情况下达到同样的⽬目的。 ⾃自定义 Hook 是⼀一个函数,其名称以 “use” 开头,函数内部可以调⽤用其他的 Hook
xxxxxxxxxx
import React, { useState, useEffect, useMemo } from "react";
export default function CustomHookPage(props) {
//定义⼀一个叫count的state变量量,初始化为0
const [count, setCount] = useState(0);
//和didMount、 didUpdate类似
useEffect(() => {
console.log("count effect");
// 只需要在count发⽣生改变的时候执⾏行行就可以啦
document.title = `点击了了${count}次`;
}, [count]);
return (
<div>
<h3>⾃自定义Hook</h3>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>add</button>
<p>{useClock().toLocaleTimeString()}</p>
</div>
);
}
//⾃自定义hook,命名必须以use开头
function useClock() {
const [date, setDate] = useState(new Date());
useEffect(() => {
console.log("date effect");
//只需要在didMount时候执⾏行行就可以了了
const timer = setInterval(() => {
setDate(new Date());
}, 1000);
//清除定时器器,类似willUnmount
return () => clearInterval(timer);
}, []);
return date;
}<h3>⾃自定义Hook</h3>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>add</button>
<p>{useClock().toLocaleTimeString()}</p>
</div>
);
}
//⾃自定义hook,命名必须以use开头
function useClock() {
const [date, setDate] = useState(new Date());
useEffect(() => {
console.log("date effect");
//只需要在didMount时候执⾏行行就可以了了
const timer = setInterval(() => {
setDate(new Date());
}, 1000);
//清除定时器器,类似willUnmount
return () => clearInterval(timer);
}, []);
return date;
}
Hook 使用规则
Hook 就是 JavaScript 函数,但是使⽤用它们会有两个额外的规则: 只能在函数最外层调⽤用 Hook。不不要在循环、条件判断或者⼦子函数中调⽤用。 只能在 React 的函数组件中调⽤用 Hook。不不要在其他 JavaScript 函数中调⽤用。(还有⼀一个地⽅方可 以调⽤用 Hook —— 就是⾃自定义的 Hook 中。)
Hook API之useMemo与useCallback
useMemo
把“创建”函数和依赖项数组作为参数传⼊入 useMemo ,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进⾏行行⾼高开销的计算。
xxxxxxxxxx
import React, { useState, useMemo } from "react";
export default function UseMemoPage(props) {
const [count, setCount] = useState(0);
const expensive = useMemo(() => {
console.log("compute");
let sum = 0;
for (let i = 0; i < count; i++) {
sum += i;
}
return sum;
//只有count变化,这⾥里里才重新执⾏行行
}, [count]);
const [value, setValue] = useState("");
return (
<div>
<h3>UseMemoPage</h3>
<p>expensive:{expensive}</p>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>add</button>
<input value={value} onChange={event => setValue(event.target.value)} />
</div>
);
}
useCallback
把内联回调函数及依赖项数组作为参数传⼊入 useCallback ,它将返回该回调函数的 memoized 版本, 该回调函数仅在某个依赖项改变时才会更更新。当你把回调函数传递给经过优化的并使⽤用引⽤用相等性去避 免⾮非必要渲染(例例如 shouldComponentUpdate )的⼦子组件时,它将⾮非常有⽤用。
xxxxxxxxxx
import React, { useState, useCallback, PureComponent } from "react";
export default function UseCallbackPage(props) {
const [count, setCount] = useState(0);
const addClick = useCallback(() => {
let sum = 0;
for (let i = 0; i < count; i++) {
sum += i;
}
return sum;
}, [count]);
const [value, setValue] = useState("");
return (
<div>
<h3>UseCallbackPage</h3>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>add</button>
<input value={value} onChange={event => setValue(event.target.value)} />
<Child addClick={addClick} />
</div>
);
}
class Child extends PureComponent {
render() {
console.log("child render");
const { addClick } = this.props;
return (
<div>
<h3>Child</h3>
<button onClick={() => console.log(addClick())}>add</button>
</div>
);
}
}
注意 依赖项数组不不会作为参数传给“创建”函数。虽然从概念上来说它表现为:所有“创建”函数中引⽤用的 值都应该出现在依赖项数组中。未来编译器器会更更加智能,届时⾃自动创建数组将成为可能