1 react 入门
1.1起步
创建项目:npx create-react-app my-app
打开项目: cd my-app
启动项目:npm start
暴露配置项:npm run eject
理解npx create-react-app my-app 指令
npx是npm的附带产物
执行 npx create-react-app my-app命令流程如下:
- 一旦使用npx去执行一段命令,那么npx会首先看第一个参数对应的工具是否被安装
- 如果没有被安装,npx就会告诉npm临时安装一下,临时安装进内存
- 当临时安装好了以后,npx会再次直接执行整段命令"create-react-app my-app"
create-react-app是react的官方脚手架
1.2文件结构
README.md--文档
public--存放静态资源
src--具体代码
App.js--根组件
index.css--全局样式
index.js--入口文件
package.json---npm 依赖
config-项目的具体配置
项目中config文件夹一般是隐藏掉的,如果想要出现可使用yarn eject,一旦执行,不可恢复
1.3React和ReactDom
import React from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(
<h1>hello React</h1>,
document.getElementById('root')
);
React负责逻辑控制,数据->VDOM,是react的核心库
ReactDom渲染实际Dom,VDOM->DOM
React可以使用jsx来描述ui
<script type="text/babel">
//script上有type="text/babel"属性, 将意味babel将接管所有的代码解析
const reactDivElement = (
<div>
hello jsx
<span>i am child span</span>
</div>
)
const root1 = ReactDOM.createRoot(document.getElementById("root1"))
root1.render(reactDivElement)
</script>
babel解析JSX原理
babel会监听全局的document.contentLoad,具体流程如下:
- 当前页面的所有的script标签全部生成完毕,babel会通过document.getElementByTagName,拿到所有的script标签
- 分别通过getAttributes函数,读取script上面的属性
- 如果type=“text/babel”,就把里面的代码全部拿过来,通过tansform方法转换一遍,通过新建一个script标签,将转换后的代码插入script中,将script插入到head标签
- 如果不写type,type默认位text/Javascript,此时以Javascript的形式解析
- 如果以上两种都不是,浏览器不会看script里面的代码
babel-loader把jsx编译成相应的js对象.
React.createElement再把这个JS对象构造成React需要的虚拟dom
2 jsx
jsx是一种javascript的语法扩展,其语言比较像模板语言,但实际上完全是javaScript内部实现的,jsx可以很好的描述UI,能够有效地提高开发效率
2.1基本使用
//index.js
import React from 'react';
import ReactDOM from 'react-dom';
//基本使用
const name = "react"
const jsx = <div>hello,{name}</div>
ReactDOM.render(
jsx,
document.getElementById('root')
);
2.2函数
函数也是合法的表达式
import React from 'react';
import ReactDOM from 'react-dom';
//函数
const user = {
firstName:"Harry",
lastName:"Potter"
}
function formatName(name){
return name.firstName+" "+ name.lastName
}
const jsx = <div>{formatName(user)}</div>
ReactDOM.render(
jsx,
document.getElementById('root')
);
2.3对象
jsx也是js对象,也是合法表达式
import React from 'react';
import ReactDOM from 'react-dom';
const greet = <div>good</div>
const jsx = <div>{greet}</div>
ReactDOM.render(
jsx,
document.getElementById('root')
);
2.4条件语句
import React from 'react';
import ReactDOM from 'react-dom';
//条件语句
const show = true;//false;
const greet = <div>good</div>;
const jsx = (
<div>
{
show?greet:"登录"
}
{show&&greet}
</div>
)
ReactDOM.render(
jsx,
document.getElementById('root')
);
2.5数组
数组作为一组子元素对待,数组中存放一组jsx用于显示列表数据
import React from 'react';
import ReactDOM from 'react-dom';
//数组
const a = [0,1,2]
const jsx = (
<div>
<ul>
{
//diff时候,首先比较type,然后是key,所以同级同类型元素,key值必须唯一
a.map(item=>(
<li key={item}>{item}</li>
))
}
</ul>
</div>
)
ReactDOM.render(
jsx,
document.getElementById('root')
);
2.6属性的使用
属性:静态值用双引号,动态值用花括号,class、for要特殊处理
注意:class是保留字,如果要增加class,需要使用ClassName
import React from 'react';
import ReactDOM from 'react-dom';
import logo from './logo.svg'
const jsx = (
<div>
<img src = {logo} style={{width:100}} className="img"/>
</div>
)
ReactDOM.render(
jsx,
document.getElementById('root')
);
2.7模块化
css模块化,创建index.css
import React from 'react';
import ReactDOM from 'react-dom';
import logo from './logo.svg'
import './index.css'
const jsx = (
<div className = {styles.app}>
<img
src = {logo}
className = "logo"
style={{width:"50px",height:"30px"}}
alt="这个一个图片"
/>
</div>
)
ReactDOM.render(
jsx,
document.getElementById('root')
);
或者npm install sass -D
import React from 'react';
import ReactDOM from 'react-dom';
import logo from './logo.svg'
import styles from './index.module.scss'
const jsx = (
<div className = {styles.app}>
<img
src = {logo}
className={styles.logo}
style={{width:"50px",height:"30px"}}
alt="这个一个图片"
/>
</div>
)
ReactDOM.render(
jsx,
document.getElementById('root')
);
3 组件
组件,从概念上类似javaScript函数,它可以接受任何形式的入参(props),并返回用于描述页面展示内容的React元素,组件有两种形式:类组件(class组件)和函数组件(function 组件)
3.1class组件
class组件(有状态组件)通常拥有状态和生命周期。(18以后很少使用,比较推崇无状态组件)
import React,{Component} from "react"
export default class ClassComponent extends Component{
constructor(props){
super(props);
//使用state属性维护状态,在构造函数中初始化状态
this.state = {
date:new Date()
}
}
componentDidMount(){
//组件挂载之后启动定时器每秒更新状态
this.timerID = setInterval(()=>{
//使用setState方法更新状态
this.setState({
date:new Date()
})
},1000)
}
componentWillUnmount(){
//组件卸载前停止定时器
clearInterval(this.timerID)
}
componentDidUpdate(){
console.log("compoentDidUpdate");
}
render(){
return (
<div>
{this.state.date.toLocaleTimeString()}
</div>
)
}
}
// 上述代码可简写为
import React,{Component} from "react"
export default class ClassComponent extends Component{
state = {
date:new Date()
}
componentDidMount(){
//组件挂载之后启动定时器每秒更新状态
this.timerID = setInterval(()=>{
//使用setState方法更新状态
this.setState({
date:new Date()
})
},1000)
}
componentWillUnmount(){
//组件卸载前停止定时器
clearInterval(this.timerID)
}
componentDidUpdate(){
console.log("compoentDidUpdate");
}
render(){
return (
<div>
{this.state.date.toLocaleTimeString()}
</div>
)
}
}
3.2 function组件
函数组件(function组件)通常无状态和生命周期(react16.8开始引入hooks,函数组件也能拥有状态和相应的生命周期)
hook 允许开发者在不写类组件的情况下,生成状态(state以及一些其他曾经是类组件专属的东西
3.2.1 useState
useState会返回一个数组,里面有两个成员
-
以初始化为值的变量 。在调用的时候可以传递函数,也可以传递具体的值。如果传递的是函数,则会直接执行这个函数,将返回值作为初始化的状态。
注意: 虽然在初始化的时候允许传递函数(纯函数),我们也尽量不要传递函数,因为初始化只会执行一次。初始化不仅需要纯函数,还得是没有任何参数 【因为react在调用你的这个initial function的时候是不会给你传参数】 -
修改该变量的函数。这个函数的调用会导致函数组件的重新渲染。调用该函数的时候,可以直接传递一个值,也可以传递一个函数。如果你传递的是一个函数,则react会将上一次的状态传递给你 帮助你,进行计算。(如果你传递的是一个函数,react会将这个函数放到一个队列里面等待执行,那如果我们想每次都时时拿到上一次的值,我们得写成一个函数 。此时状态得更新是批量进行的。
3.2.2 useEffect
useEffect:处理副作用
副作用:完全不依赖React功能的外部操作【这些外部操作不经过react的手,但是却对react产生了一些影响】
例如:
1.http请求
2.dom操作
3.异步请求多数都是会产生副作用的
虽然我们不是所有的副作用操作都是在useEffect里进行,但是官方建议我们尽可以将副作用放在useEffect中运行。 因为副作用操作是会产生意外之外的结果,如果我们想要更好的追踪副作用的执行时机,就可以将副作用都归纳进useEffect里方便追踪
useEffect(setup,dependencies)
setup:初始化的意思,是一个函数
dependencies:依赖,是一个数组
useEffect的执行时机
- 当我们使用useEffect去注册setup以后,React会在该组件每次挂载完毕(渲染完毕)到页面时都会执行对应的setup函数,但是是异步执行的setup
- 当依赖项发生变更时,useEffect会重新执行对应的setup
**setup函数会有一个返回值,这个返回值称为清理函数,清理函数会在组件卸载的时候执行 **
实际场景
1.http请求
2.访问真实dom
副作用的清除
1.dom事件的绑定清除
2.计时器绑定清除
下面是useState、useEffect实例:
import React,{useState,useEffect} from 'react'
export function FunctionComponent(props){
const [date,setDate] = useState(new Date());
useEffect(()=>{
const timer = setInterval(()=>{
setDate(new Date());
},1000)
return()=>clearInterval(timer);
},[])
return(
<div>
<h3>FunctionComponent</h3>
<p>{date.toLocaleTimeString()}</p>
</div>
)
}
setState只有在React合成事件和生命周期函数中是异步的,在原生事件和
setTimeout都是同步的,这里的异步其实是批量更新。
useEffect Hook可以看做componentDidMount、componentDidUpdate和componentwillUnmount这是三个组合
4 生命周期
4.1 生命周期方法
生命周期方法,用于在组件不同阶段执行自定义功能。在组件被创建并插入到DOM时(挂载中阶段)组件更新时,组件取消挂载或从DOM中删除时,都可以使用的生命周期方法
ReactV16.3之前的生命周期
ReactV16.4之后的生命周期
v17可能会废弃的三个生命周期函数用getDerivedStateFormProps替代,目前使用的话加上UNSAFE_:
componentWillMount
componentWillReceiveProps
componentWillUpdate
4.2 两个新的生命周期函数
static getDerivedStateFromProps
getDerivedStateFromProps会在render方法之前调用,并在初始化挂载及后续更新时都会被调用。它应返回一个对象来更新state,如果返回的为null,不更新任何内容
注意:不管什么原因,每次渲染前都会触发这个方法,相对于UNSAFE_componentWillReceiveProps而言,后者仅在父组件重新渲染时触发,而不在内部调用setState时触发
getSnapshotBeforeUpdate
getSnapshotBeforeUpdate在render之后,在componentDidUpdate之前触发,会在最近一次渲染输出(提交到DOM节点)之前调用。它使得组件能够在发生更改之前从DOM中捕获一些信息。此生命周期的任何返回值都将作为参数传递给componentDidUpdate(prevProps,prevState,snapshot)
如果不想手动给将要废弃的生命周期添加UNSAFE_前缀,可以使用下面的命令
npx react-codemd react-unsafe-lifecycles
代码学习:
import React ,{Component, component} from "react"
import PropTypes from 'prop-types'
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 getDerivedStateForProps(props,state){
const {count} = state
console.log("getDerivedStaticFormProps",count)
return count<5 ?null :{count:0}
}
getSnapshotBeforeUpdate(prevProps,prevState,snapshot){
const {count} = prevState
console.log("getSnapshotBeforeUpdate",count)
return null
}
componentDidMount(){
console.log("componentDidMount",this.state.count)
}
componentWillUnmount(){
console.log("componentWillUnmount",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>LifeCyclePage</h1>
<p>{count}</p>
<button onClick={this.setCount}>改变count</button>
<Child count = {count}/>
</div>
)
}
}
class Child extends Component{
UNSAFE_componentWillReceiveProps(nextProps){
//在已挂载的组件接收新的 props 之前被调⽤
console.log("componentWillReceiveProps")
}
componentWillUnmount(){
//组件卸载之前
console.log("componentWillUnmount")
}
render(){
return(
<div
style={{border:"1px solid black",margin:"10px",padding:"10px" }}
>
我是child组件
<div>child count:{this.props.count}</div>
</div>
)
}
}
5 复合组件
复合组件给你足够的敏捷去自定义组件的外观和行为,这种方式更明确和安全。如果组件中有公用的非ui逻辑,将他们抽取为js模块导入而不是继承它们
5.1不具名组件
import React,{Component} from "react";
export default class BottomBar extends Component{
render(){
return(
<div className = "bottomBar">
<h3>BottomBar</h3>
</div>
)
}
}
import React,{Component} from "react";
export default class TopBar extends Component{
render(){
return(
<div className = "topBar">
<h3>TopBar</h3>
</div>
)
}
}
import React,{Component, 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;
return(
<div>
{showBottomBar&&<TopBar/>}
{children.content}
{children.txt}
<button onClick = {children.btnClick}>button</button>
{showBottomBar&&<BottomBar/>}
</div>
)
}
}
import React,{Component, 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>
)
}
}
5.2具名组件
import React,{Component, component} from "react"
import Layout from './Layout'
export default class UserPage extends Component{
render(){
return(
<Layout showTopBar = {true} showBottomBar = {true} title="用户中心">
{{
content:(
<div>
<h3>HomePage</h3>
</div>
),
txt:"这是一个文本",
btnClick:()=>{
console.log("btnClick")
}
}}
</Layout>
)
}
}
5.3复合组件
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>
{{
defaultBtns:{
searchClick:() => console.log("默认查询"),
resetClick:() => console.log("默认重置")
},
btns:[
{
title:'查询',
onClick:() => console.log('查询')
},{
title:'重置',
onClick:() => console.log('重置')
}
]
}}
</FormButton>
</div>
)
}
}
6 redux
redux是负责组织state的工具,使用的时候,需要Redux的好处和坏处,酌情使用。一般在下面场景中,会引入redux
- 有相当大量的、随时间变化的数据
- 你的state需要有一个单一可靠数据来源
- 你觉得把所有的state都放在最顶层组件中无法满足需求
- 某个组件的状态需要共享
6.1 redux
redux是Javascript应用的状态管理容器,提供可预测化的状态管理,他保证了程序行为的一致性且易用测试。
6.2 安装redux
npm install redux -s
6.3 redux上手
如果要做一个累加器,思维步骤
- 需要一个store来存储数据
- store里面的reducer初始化并定义state修改规则
- 通过dispatch一个action来提交对数据的修改
- action提交到reducer函数里,根据传入的action的type,返回新的state
//ReduxStore.js
import {createStore} from 'redux'
const counterReducer = (state = 0,action)=>{
console.log(action.type)
switch(action.type){
case 'ADD':
return state + 1
case 'MINUS':
return state - 1
default:
return state
}
}
const store = createStore(counterReducer)
export default store
//ReduxPage.js
import React,{Component} from "react"
import store from "../store/ReduxStore"
export default class ReduxPage extends Component{
componentDidMount(){
store.subscribe(()=>{
console.log("subscribe");
this.forceUpdate();
})
}
add = () =>{
store.dispatch({type:"ADD"})
}
minus = () =>{
store.dispatch({type:"MINUS"})
}
render(){
console.log("store",store)
return(
<div>
<h3>ReduxPage</h3>
<p>{store.getState()}</p>
<button onClick={this.add}>add</button>
<button onClick={this.minus}>minus</button>
</div>
)
}
}
6.4 react-redux
6.4.1 安装react-redux
npm install react-redux -s
6.4.2 使用react-redux
react-redux提供了两个api
Provider为后代组件提供store
connect为组件提供数据和变更方法
import React,{Component} from "react"
import {connect} from "react-redux"
const mapStateToProps = state =>{
return {
num:state
}
}
const mapDispatchToProps = {
add:()=>{
return {
type:"ADD"
}
},
minus:()=>{
return {
type:"MINUS"
}
}
}
class ReactReduxPage extends Component{
render(){
const {num,add,minus} = this.props;
return(
<div>
<h1>ReactReduxPage</h1>
<p>{num}</p>
<button onClick={add}>add</button>
<button onClick={minus}>minus</button>
</div>
)
}
}
export default connect(
mapStateToProps,//状态映射
mapDispatchToProps//派发事件映射
)(ReactReduxPage)
import React from 'react'
import ReactDom from 'react-dom'
import App from './App'
import store from './store/'
import { Provider } from 'react-redux'
ReactDom.render(
<Provider store={store}>
<App/>
</Provider>,
document.querySelector('#root')
)
7 react-router
7.1 安装
npm install react-router-dom -s
7.2 基本使用
react-router中奉行一切皆组件的思想,以下都是以组件形式存在
路由器-Router
链接-Link
路由-Route
独占-Switch
重定向-Redirect
import React,{Component} from "react"
import {BrowserRouter as Router,Route,Link} from "react-router-dom"
export default class RouterPage extends Component{
render(){
return(
<div>
<h3>RouterPage</h3>
<Router>
<Link to="/">首页</Link>
<Link to="/user">用户中心</Link>
{/* 根据exact,实现精准匹配 */}
<Route
exact
path="/"
component ={HomePage}
// children ={()=><div>children</div>}
// render ={()=><div>render</div>}
/>
<Route path="/user" component = {UserPage}/>
</Router>
</div>
)
}
}
class HomePage extends Component{
render(){
return(
<div>
<h3>HomePage</h3>
</div>
)
}
}
class UserPage extends Component{
render(){
return(
<div>
<h3>UserPage</h3>
</div>
)
}
}
7.3 Route渲染内容的三种方式
Route渲染优先级:children>component>render
这三种方式互斥,你只能用其中一种
- children:func
有时候,不管location是否匹配,你都需要渲染一些内容,这个时候你可以用children,除了不管location是否匹配都会渲染之外,其他方法和render完全一样
- render:func
当你使用render时,你只是调用了个函数,只有当location匹配的时候渲染
- component:component
只有当location匹配的时候渲染
7.4 404页面
设定一个没有path的路由在路由列表最后面,表示一定匹配
import React,{Component} from "react"
import {BrowserRouter as Router,Route,Link,Switch} from "react-router-dom"
export default class RouterPage extends Component{
render(){
return(
<div>
<h3>RouterPage</h3>
<Router>
<Link to="/">首页</Link>
<Link to="/user">用户中心</Link>
{/* 根据exact,实现精准匹配 */}
<Switch>
<Route
exact
path="/"
component ={HomePage}
// children ={()=><div>children</div>}
// render ={()=><div>render</div>}
/>
<Route path="/user" component = {UserPage}/>
<Route component = {EmptyPage}/>
</Switch>
</Router>
</div>
)
}
}
class HomePage extends Component{
render(){
return(
<div>
<h3>HomePage</h3>
</div>
)
}
}
class UserPage extends Component{
render(){
return(
<div>
<h3>UserPage</h3>
</div>
)
}
}
class EmptyPage extends Component{
render(){
return(
<div>
<h3>EmptyPage-404</h3>
</div>
)
}
}
8 PureComponent
8.1 实现性能优化
定制shouldComponentUpdate后的Component
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>PureComponentPage</h1>
<div onClick={this.setCounter}>counter:{counter}</div>
</div>
)
}
}
8.2 浅比较
缺点是必须要用class形式,而且要注意是浅比较
// 比较值相等,或者对象含有相同的键,其属性值相等
function shallowEqual(objA:mixed, objB:mixed):boolean {
if (Object.is(objA, objB)) {
return true;
}
if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) {
return false;
}
var keysA = Object.keys(objA);
var keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
}
for (var i = 0; i < keysA.length; i++) {
if (!hasOwnProperty.call(objB, keysA[i]) || !is(objA[keysA[i]], objB[keysA[i]])) {
return false;
}
}
return true;
}
8.3 和Component相比
React.PureComponent 与 React.Component 很相似。两者的区别在于 React.Component 并未实现 shouldComponentUpdate(),而React.PureComponent 中以浅层对⽐ prop 和 state 的方式来实现了该函数。如果赋予 React 组件相同的 props 和 state, render() 函数会渲染相同的内容,那么在某些情况下使用 React.PureComponent 可提高性能
9 Hook
9.1 认识Hook
Hook是一个特殊的函数,它可以让你"钩入"React的特性,例如,useState是允许你在react函数组件中添加state的Hook,如果你在编写函数组件并意识到需要向其添加一些state,以前的做法是必须转化为class,但是现在你可以在现有函数组件中使用Hook
import React,{useState} from "react"
export default function HookPage(props){
const [count,setCount] = useState(0)
return (
<div>
<h3>HookPage</h3>
<p>{count}</p>
<button onClick={()=>setCount(count+1)}>add</button>
</div>
)
}
11.2 使用Effect Hook
Effect Hook可以让你在函数组件中执行副操作,数据获取,设置订阅以及手动更改React组件中的Dom都属于副作用
import React,{useState,useEffect} from "react"
export default function HookPage(props){
const [count,setCount] = useState(0)
//与componentDidMount和componentDidUpdate相似
useEffect(()=>{
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 将在每轮渲染结束后执行,但你可以选择让它在只有某些值改变的时候才执行
- effect的条件执行
import React,{useState,useEffect} from "react"
export default function HookPage(props){
const [count,setCount] = useState(0)
const [date,setDate] =useState(new Date())
useEffect(()=>{
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创建的诸如订阅或者计时器等资源,要实现这一点,需要返回一个清楚函数,防止内存泄露,清除函数会在组件卸载前执行。
import React,{useState,useEffect} from "react"
export default function HookPage(props){
const [count,setCount] = useState(0)
const [date,setDate] =useState(new Date())
useEffect(()=>{
document.title = `You clicked ${count} times`
},[count])
useEffect(()=>{
const timer = setInterval(()=>{
setDate(new Date());
},1000)
return()=>clearInterval(timer)
},[])
return (
<div>
<h3>HookPage</h3>
<p>{count}</p>
<button onClick={()=>setCount(count+1)}>add</button>
<p>{date.toLocaleTimeString()}</p>
</div>
)
}
10 自定义Hook与Hook使用规则
10.1 自定义Hook
有时候我们会想在组件之间重用一些状态逻辑,目前有两种主流方案来解决这个问题:高阶组件和render props。自定义Hook可以让你在不增加组件的情况下达到同样的目的。
自定义Hook是一个函数,其名称"use"开头,函数内部可以调用其他的Hook
import React,{useState,useEffect,useDemo} from "react"
export default function CustomHookPage(props){
const [count,setCount] = useState(0);
useEffect(() => {
console.log("count effect");
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
function useClock(){
const [date, setDate] = useState(new Date())
useEffect(() => {
console.log("date effect")
const timer = setInterval(()=>{
setDate(new Date())
},1000)
return () => clearInterval(timer);
}, [])
return date
}
10.2 Hook使用规则
Hook就是JavaScript函数,但是使用他们会有两个额外的规则:
- 只能在函数最外层调用Hook。不要在循环、条件判断或者子函数中调用。
- 只能在React的函数组件中调用Hook。不要在其他JavaScript函数中使用。
11.Hook API的useDemo与useCallback
11.1 useDemo
把"创建"函数和依赖项数组作为参数传入useDemo,它仅会在某个依赖项改变时才重新计算memoized值。这种优化有助于避免在每次渲染时都进行高开销的计算
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])
const [value,setValue] = useState("")
return (
<div>
<h3>UseMemoPage</h3>
<p>expensive:{expensive}</p>
<button onClick = {()=>setCount(count+1)}>add</button>
<input value={value} onChange={event=>setValue(event.target.value)}/>
</div>
)
}
11.2 useCallback
把内联回调函数及其依赖项作为参数传入useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲(shouldComponentUpdate )的子组件时,它将非常有用。
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(){
const {addClick} = this.props;
return (
<div>
<h3>Child</h3>
<button onClick={() => console.log(addClick())}>add</button>
</div>
)
}
}
代码下载地址:https://gitee.com/JingYaBei/my-app.git