07-项目打包 & React Hooks

项目打包

项目打包是为了把整个项目都打包成最纯粹的js,让浏览器可以直接执行

打包命令已经在package.json里面定义好了

运行命令:npm run build,执行时间取决于第三方插件的数量以及电脑配置

打包完之后再build文件夹下,这个文件夹下都是静态资源文件,需要web服务器运行

我们可以用serve来临时运行这个项目,看看好不好用

这是运行打包命令后的提示,提示我们可以用serve运行项目

尝试一下用serve,当然这种是临时的

访问一下

当然,实际开发肯定也不会只是用这种打包方式,这种打包太简陋了。一般都是用webpack,并且打包之后也不会用serve部署(这里只是临时看看),而是把静态文件交给后端的小伙伴,后端的小伙伴把文件部署在Nginx这种服务器上

类组件知识补充

setState

首先看一个案例证明setState操作是异步的

这是一个很简单的加和操作

首先我在加和的这个动作里console.log一下新的state

javascript 复制代码
import React, { Component } from "react";

export default class Test extends Component {
    state = {
        count: 0,
    };
    add = () => {
        const { count } = this.state;
        this.setState({ count: count + 1 });
        console.log("+1操作", this.state.count);
    };
    render() {
        return (
            <div>
                <h1>当前count值{this.state.count}</h1>
                <button onClick={this.add}>+1操作</button>
            </div>
        );
    }
}

可以发现,setState并不是同步的操作,而是异步的

这里我们看一下setState更新状态的2种写法

javascript 复制代码
(1). setState(stateChange, [callback])------对象式的setState
           1.stateChange为状态改变对象(该对象可以体现出状态的更改)
           2.callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用
				
(2). setState(updater, [callback])------函数式的setState
           1.updater为返回stateChange对象的函数。
           2.updater可以接收到state和props。
           4.callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。
总结:
	1.对象式的setState是函数式的setState的简写方式(语法糖)
	2.使用原则:
			(1).如果新状态不依赖于原状态 ===> 使用对象方式
			(2).如果新状态依赖于原状态 ===> 使用函数方式
			(3).如果需要在setState()执行后获取最新的状态数据, 
				要在第二个callback函数中读取

上面的比较抽象,我们还是看代码,其他部分没变,集中在setState的部分

javascript 复制代码
  add = () => {
      const { count } = this.state;
      this.setState({ count: count + 1 }, () => {
          // 在直接set完state后,立即执行一个回调函数
          // 这个回调函数是等setState动作做完之后才执行的
          console.log("+1操作执行了", this.state.count);
      });
  };

综上可以确定,setState是异步的操作

javascript 复制代码
(1). setState(stateChange, [callback])------对象式的setState
//实际上对象式setState就是函数式setState的简写
add = () => {
     const { count } = this.state;
	this.setState({ count: count + 1 },()=>{
		可以选传一个函数进来
	})
 };
javascript 复制代码
(2). setState(updater, [callback])------函数式的setState
add = () => {
    // 函数式的setState
    this.setState((state, props) => {
        return { count: state.count + 1 };
    },()=>{
		可以选传一个函数进来
	});
};

LazyLoad懒加载

首先,代码中懒加载的机制是无论页面上有多少个路由标签。1个也好,100个也好,在页面加载出来的时候,都是一次性全部加载出来

页面一加载,路由就会把要用的组件 全都加载出来,后续用的话就不用单独请求加载了,但是这样如果组件过多就会造成负载很大,不太好。所以引入我们的懒加载机制

所以我们之前的import组件方式就都不要了,因为只要一用import导入就会自动加载,采用懒加载的导入方式

对比一下加载方式

javascript 复制代码
// 这种import形式会默认一上来就加载,所以我们不能用这种了
// import About from "./pages/lazyload/About/About";
// import Home from "./pages/lazyload/Home/Home";

// 改用懒加载写法,并用变量接住
const About = lazy(() => import("./pages/lazyload/About/About"));
const Home = lazy(() => import("./pages/lazyload/Home/Home"));

但是此时改完懒加载后,点击运行会报错,提示没有用<Suspense fallback={标签}>

之所以强制用这个,是因为:我们的组件都是现用现请求,但这就带来了一个问题,网速慢我请求不来,或者请求不到数据怎么办?

我们就需要用一个兜底的方法,在这种请求失败或者网慢的时候给用户看。这个兜底的操作就是由Suspense 标签来做,注意,Suspense标签来自于React依赖
import React, { Component, Suspense, lazy } from "react";

javascript 复制代码
// fallback引入的是兜底标签,比如模态窗口等等
<Suspense fallback={<h1>loading.....</h1>}>
	<Switch>
	    <Route path="/xxx" component={Xxxx}/>
        <Route path="/about" component={About}></Route>
        <Route path="/home" component={Home}></Route>
	    <Redirect to="/login"/>
	</Switch>
</Suspense>

此时点击某个route link,就会触发加载

改用慢速网,或者离线状态测试,可以发现兜底的标签已经显示出来了

Context

理解

一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信

比如A组件与C,D组件通信这种就叫祖组件通信,就需要用Context

AB就是正常的父子组件用props通信

再比如,组件树结构如下,现在想从根节点传递一个 userName 的属性到叶子节点 A D F,通过 props 的方式传递,会不可避免的传递通过 B C E,即使这些组件也没有使用这个 userName 属性。如果这样的嵌套树形结构有5层或10层,那么将是灾难式的开发维护体验。如果能不经过中间的节点直接到达需要的地方就可以避免这种问题,这时 Context api 就是来解决这个问题的。

Context api 是在组件树中传递数据但不用每层都经过的一种 api。

我们用完Context之后,就会出现类似这种的数据传递流程

首先使用类组件实现一个祖组件通信的例子(就是套娃,A用props给B,B用props给C,C用props给D)

javascript 复制代码
import React, { Component } from "react";

export default class A extends Component {
  state = {
    data: "123456",
  };
  render() {
    return (
      <div>
        <B data={this.state} />
      </div>
    );
  }
}

class B extends Component {
  render() {
    return (
      <div>
        <C data={this.props.data} />
      </div>
    );
  }
}

class C extends Component {
  render() {
    console.log("C组件", this.props.data);
    return <div>C</div>;
  }
}

但是这种写法非常不推荐,如果有n个组件,就要套n层,或者B组件中断了,就没法继续传递了,所以我们引入了Context传递

Context使用

javascript 复制代码
1) 创建Context容器对象:
	const XxxContext = React.createContext()  
	
2) 渲染子组时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:
	<xxxContext.Provider value={数据}>
		子组件
    </xxxContext.Provider>
    
3) 后代组件读取数据:

	//第一种方式:仅适用于类组件 
	  static contextType = xxxContext  // 声明接收context
	  this.context // 读取context中的value数据
	  
	//第二种方式: 函数组件与类组件都可以
	  <xxxContext.Consumer>
	    {
	      value => ( // value就是context中的value数据
	        要显示的内容
	      )
	    }
	  </xxxContext.Consumer>

创建一个 Context 对象。当 React 渲染一个订阅了这个 Context 对象的组件,这个组件会从组件树中离自身最近的那个匹配的 Provider 中读取到当前的 context 值。

这里以第二种方式为例,分文件跨组件调用

A,B组件在同一文件,C组件单独一个文件

A,B组件

javascript 复制代码
import React, { Component } from "react";
import C from "./C";

export const MyContext = React.createContext();
export default class A extends Component {
  state = {
    data: "123456",
  };
  render() {
    return (
      <div>
        <MyContext.Provider value={this.state}>
          {/* 不再需要声明props了 <B data={this.state} /> */}
          <B />
        </MyContext.Provider>
      </div>
    );
  }
}

class B extends Component {
  render() {
    return (
      <div>
        {/* 不再需要声明props了 <C data={this.state} /> */}
        {/* 这里用到C只是为了桥接祖孙组件的关系 */}
        <C />
      </div>
    );
  }
}

C组件

javascript 复制代码
import React, { Component } from "react";
import { MyContext } from "./A";

export default class C extends Component {
  render() {
    return (
      <div>
        C组件
        {/* 导入A定义好的MyContext,并用.Consumer定义为消费者
        在这个<MyContext.Consumer>标签里就可以使用祖组件的传值了,value即为全部信息,当props用即可 */}
        <MyContext.Consumer>
          {(
            value // value就是MyContext中的value数据
          ) => console.log(value)}
        </MyContext.Consumer>
      </div>
    );
  }
}

这样就可以实现祖组件与孙组件之间通信

但是实际上,在应用开发中一般不用context, 一般都用它的封装react插件

而且后续还有useContext,所以这里只做补充知识了解

Fragment

编译完实际上就是一个<></>空标签(编译之后就什么都没有了)

效果上二者等价,但是Fragment的意义是可以传一个参数,而完全空的标签是无法传递参数的

可以传递参数也就成为了Fragment存在的意义(比如循环的时候,传一个)

javascript 复制代码
import { Fragment } from 'react'
//类似于一个<div>
<Fragment></Fragment>    //可以添加一个属性 'key',用于遍历比如 <Fragment key={i}>{v}</Fragment>
<></>  //不能添加任何属性

编译之后就自动消失了


举一个遍历时用Fragment传入key的操作

javascript 复制代码
export default function App() {
  const arr = React.useState([
    { id: "1", name: "a" },
    { id: "2", name: "b" },
    { id: "3", name: "c" },
  ]);

  return (
    <Fragment>
      {arr.map((id, name) => {
        return <Fragment key={id}>name:{name}</Fragment>;
      })}
    </Fragment>
  );
}

遍历完成后,所有的标签有了key的同时,Fragment也自动去掉了

从作用上来看:

可以不用必须有一个真实的DOM根标签了,同时也可以满足空标签传递一个变量的这种需求

PureComponent

父组件里用到了子组件,父组件每次state改变,导致重新render的时候,就会触发子组件渲染。但其实有些时候,子组件没有用到父组件的state,就不需要重新渲染,所以这种重新渲染是浪费的,可以看这个例子

javascript 复制代码
import React, { Component } from "react";

export default class A extends Component {
  state = {
    data: "123456",
  };
  updateState = () => {
    // 手动触发更新state
    this.setState({ data: "123" });
  };
  render() {
    console.log("父组件render调用");
    return (
      <div>
        {/* 我们这里只传递了一个固定值,没有用到state的值 */}
        <B data="data" />
        {/* 手动触发更新state */}
        <button onClick={this.updateState}>UPDATE</button>
      </div>
    );
  }
}

// 子组件
class B extends Component {
  render() {
    console.log("子组件render调用");
    return <div>B组件</div>;
  }
}

这种渲染的控制阀门,可以参考之前我们生命周期里学习的
shouldComponentUpdate()这个函数,这个函数可以接收到两个值(nextProps, nextState),这两个值就是未来要用于更新的值,我们可以将其与当前值(state,props)比较,判断是否需要开启更新的阀门(true更新,false不更新)

比如:

javascript 复制代码
shouldComponentUpdate(nextProps, nextState) {
  // nextProps用不用取决于你想不想判断,不想判断不传也行
  // xxx是state里的某个属性名
  // 如果判断相同,则不需要更新,所以true的条件取反为false,关闭更新阀门
  return !this.state.xxx === nextState.xxx;
}

from之前的笔记:

shouldComponentUpdate(nextProps, nextState)

在setState以后,state发生变化,组件会进入重新渲染的流程时执行的逻辑。在这个生命周期中return false可以阻止组件的更新,主要用于性能优化。


所以针对于这种情况,React官方已经提供好了,我们直接继承PureComponent即可,已经给我们重写好了

javascript 复制代码
import React, { PureComponent } from "react";

// 继承PureComponent
export default class A extends PureComponent {
  state = {
    data: "123456",
  };
  updateState = () => {
    // 手动触发更新state
    // 注意,这里并不是给state设置为空,而是setState函数里传了个空对象进去
    // 代表什么都不更新的意思
    this.setState({ data: "123" });
  };

  render() {
    console.log("父组件render调用");
    return (
      <div>
        {/* 我们这里只传递了一个固定值,没有用到state的值 */}
        <B data="data" />
        {/* 手动触发更新state */}
        <button onClick={this.updateState}>UPDATE</button>
      </div>
    );
  }
}

// 继承PureComponent
class B extends PureComponent {
  render() {
    console.log("子组件render调用");
    return <div>B组件</div>;
  }
}

总结PureComponent

原始Component的2个问题

  1. 只要执行setState(),即使不改变状态数据(setState传一个空对象也会触发state更新,只不过state的内容不动), 组件也会重新render() ==> 效率低

  2. 只当前组件重新render(), 就会自动重新render子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低

效率高的做法

只有当组件的state或props数据发生改变时才重新render()

原因

Component中的shouldComponentUpdate()总是返回true

解决

复制代码
办法1: 
	重写shouldComponentUpdate()方法
	比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false
办法2:  
	使用PureComponent
	PureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回true
	注意: 
		只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false  
		不要直接修改state数据, 而是要产生新数据
项目中一般使用PureComponent来优化

再次注意:PureComponent 只是进行state和props数据的浅比较(只比较内存地址)。如果只是数据对象内部数据变了,而数据对象本身的引用地址没有变化,则被认为没有变化,返回false。不要直接修改state数据, 而是要产生新数据(新对象,新内存地址),再更新state

render props(插槽技术,封装组件必用)

场景:想给某个组件标签传入一个组件

即这种类似场景:

A标签引入了B,B在A组件里显示,结构上看起来确实是A与B为父子组件。但这就带来了一个问题,表面上是AB父子可以通过props传参,但实际上,这个操作只能在A标签里写,并且传什么值只能写死,没办法灵活修改。所以就会出现render props这种技术

javascript 复制代码
export default class Parent extends Component {
  render() {
    return (
      <div className="parent">
        Parent
        <A>
          <B></B>
        </A>
      </div>
    );
  }
}

简单来说,就是运用箭头函数的return操作,给A标签里传一个属性,这个属性return一个B标签,在目标组件里,调用这个属性的返回值,即:

javascript 复制代码
// 这个组件参数的调用,就是传入组件的插槽插进来的地方!!!
// 这其实就是jsx语法
{this.props.render()}

注意,我们一般把这个传参的属性叫render,这只是约定俗成,其实叫啥都行

修改一下:,此时在祖组件Parent里面一看就知道A与B的父子关系,同时知道了A中用到了B做插槽

javascript 复制代码
export default class Parent extends Component {
  render() {
    return (
      <div className="parent">
        Parent
        <A render={() => <B/>} />
      </div>
    );
  }
}

export default class A extends Component {
  render() {
    return (
      <div className="A">
        这里是A标签
        <br />
        {/* 
        	调用render属性,一定要加括号,变成函数返回值
            这样才能拿到传入的B标签
        */}
        // 这个组件参数的调用,就是B的插槽插进来的地方!!!
        {this.props.render()}
      </div>
    );
  }
}

运行一下,发现B组件已经被插入了A组件的插槽里了

个人感觉就是类组件属性的灵活调用,好处在于我们组件有了插槽,可以灵活在祖组件替换,不必在父组件里再写死组件。同时传参也可以正常进行

插槽传参

传参就是在箭头函数的基础上加入了参数传递,很好理解

javascript 复制代码
export default class Parent extends Component {
  render() {
    return (
      <div className="parent">
        Parent
        <A render={(data) => <B data={data} />} />
      </div>
    );
  }
}
export default class A extends Component {
  state = {
    data: "这是A传来的参数",
  };
  render() {
    return (
      <div className="A">
        这里是A标签
        <br />
        {/* 
        	插槽,并且传入A组件的参数给插槽插进来的组件,不再局限于某一固定组件
        */}
        {this.props.render(this.state.data)}
      </div>
    );
  }
}
export default class B extends Component {
  render() {
    return (
      <div className="B">
        B标签做插槽!!!
        <br />
        接收到A传来的参数: {this.props.data}
      </div>
    );
  }
}

参数传递顺序:

javascript 复制代码
A组件:{this.props.render(this.state.data)}
Parent组件接收A组件传来的参数: <A render={(data) => <B data={data} />} />
B组件解析props: {this.props.data}

总结render props

如何向组件内部动态传入自定义组件标签(插槽技术)

复制代码
Vue中: 
	使用slot技术, 也就是通过组件标签体传入结构  <A><B/></A>
React中:
	使用children props: 通过组件标签体传入结构
	使用render props: 通过组件标签属性传入结构,而且可以携带数据,一般用render函数属性

children props

复制代码
<A>
  <B>xxxx</B>
</A>
{this.props.children}
问题: 如果B组件需要A组件内的数据, ==> 做不到 ,因为这种解析仅限于H5的标签children属性,React并没有办法直接支持

render props

复制代码
A组件:{this.props.render(内部state数据)}
Parent组件接收A组件传来的参数: <A render={(data) => <B data={data} />} />
B组件解析props: 读取A组件传入的数据显示{this.props.data}

错误边界

说白了,就是个兜底的方法,当组件的子组件出现错误时,不至于崩掉,将错误控制在一个小小的子组件里,不至于让整个应用崩掉

当组件的子组件出现错误时会调用,也就是始终去找出错组件的父组件去处理
static getDerivedStateFromError(error) 的钩子,并且得到错误信息,并且将错误信息return到state里,触发页面渲染,做出相应的处理,比如错误页面,弹窗等等友好提示

但是注意,这个东西只能在prod生产环境生效,对于dev模式下,只会给你闪一下处理的页面,然后就继续该报错报错,因为要在dev模式下告诉程序员哪里出错了,才能做出相应的处理

同时,错误边界的捕捉,仅限于生命周期函数,比如render,componentDidMount......这种生命周期函数里发生的错误

javascript 复制代码
class Children extends Component {
  // 比如这种胡乱调用的某个函数(非生命周期的),就不会被catch住	
  test()
  
  render() {
    return (
      <div>
      </div>
    );
  }
}

例子:

javascript 复制代码
import React, { Component } from "react";

export default class Parent extends Component {
  state = {
    // 异常Flag
    hasError: false,
  };

  static getDerivedStateFromError(error) {
    // 子组件生命周期函数里出现的异常会在这里被捕捉,同时获取异常信息
    console.log(error);
    // 在render之前触发
    // 返回新的state,触发组件重新渲染
    return {
      hasError: true,
    };
  }
  render() {
    return (
      <div>
      <div>
        父组件
        <br />
        {/* 根据子组件是否有错误,来判断显示内容 */}
        {this.state.hasError ? <>子组件网络出错,请重试</> : <Children></Children>}
      </div>
    );
  }
}

class Children extends Component {
  state = {
    // 空的内容,想map遍历直接报错了
    // users: [
    //   { id: "123", name: "Tony" },
    //   { id: "456", name: "Jerry" },
    // ],
  };
  render() {
    return (
      <div>
        子组件
        {this.state.users.map((user) => {
          return <div key={user.id}>name:{user.name}</div>;
        })}
      </div>
    );
  }
}

开发模式下的控制台:


生产环境下例子:

componentDidCatch钩子(属于生命周期钩子,但不常见)

这个钩子一般是和getDerivedStateFromError配合使用,每次出现错误就会调用这个函数,一般用于统计错误次数以及错误信息,并打包发给后端,同样也是生产环境下才生效

javascript 复制代码
// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {
    console.log(error);
    // 在render之前触发
    // 返回新的state
    return {
        hasError: true,
    };
}

componentDidCatch(error, info) {
    // 统计页面的错误。发送请求发送到后台去等一系列操作都可以在这做
    console.log("componentDidCatch钩子运行", error, info);
}

组件通信方式总结

组件间的关系

  • 父子组件
  • 兄弟组件(非嵌套组件)
  • 祖孙组件(跨级组件)

几种通信方式

复制代码
1.props:
	(1).children props
	(2).render props
2.消息订阅-发布:
	pubs-sub、event等等
3.集中式管理:
	redux、dva等等
4.conText:
	生产者-消费者模式

比较好的搭配方式

复制代码
父子组件:props
兄弟组件:消息订阅-发布、集中式管理
祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)

React-Hooks

React16.8之前,函数式组件本身并没有this ,打印一下组件的this结果是undefined,因此函数式组件无法通过this.setState等操作本身状态,或者操作生命周期,但在16.8之后,React提出来Hooks的概念。函数式组件就可以使用Hooks来操作自身的状态,生命周期等等,函数式组件就突然变得香了起来。

useState

hooks 解决了函数式组件和类式组件的差异,让函数式组件拥有了类式组件所拥有的 state ,同时新增了一些 API ,让函数式组件,变得更加的灵活

首先我们需要明确一点,函数式组件没有 自己的 this现在常用的组件都是函数式组件,不再是类式组件

js 复制代码
function Demo() {
    const [count, setCount] = React.useState(0)
    console.log(count, setCount);
    function add() {
        setCount(count + 1)
    }
    return (
        <div>
            <h2>当前求和为:{count}</h2>
            <button onClick={add}>点我加1</button>
        </div>
    )
}
export default Demo

利用函数式组件完成的 点我加1 案例

这里利用了一个 Hook :useState

它让函数式组件能够维护自己的 state ,它接收一个参数 ,作为初始化 state 的值,赋值给 count,因此 useState 的初始值只有第一次有效 ,它所映射出的两个变量 countsetCount 我们可以理解为 setState 来使用

useState 能够返回一个数组,第一个元素是 state ,第二个是更新 state 的函数

javascript 复制代码
// 打印一下这个Hooks的内容
const [count, setCount] = React.useState(0);
console.log(count, setCount);

控制台输出:

0就是我们传入初始化的count,后面的函数就是setCount的函数


count 是初始化的值,而 setCount 就像是一个 action 对象驱动状态更新

我们可以通过 setCount 来更新 count 的值,setCount可以传入两种形式(直接传值 或 箭头函数)

js 复制代码
setCount(count + 1)

除了这种直接传值的操作,我们还可以用箭头函数的写法,二者等价,视情况使用

javascript 复制代码
setCount((count) => {
// 把计算结果return给count
return count + 1;
});

总结useState

javascript 复制代码
(1). State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作
(2). 语法: const [xxx, setXxx] = React.useState(initValue)  
(3). useState()说明:
        参数: 第一次初始化指定的值在内部作缓存,读取的时候可以直接调用
        返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
(4). setXxx()2种写法:
        setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
        setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值

useEffect

在类式组件中,提供了一些声明周期钩子给我们使用,我们可以在组件的特殊时期执行特定的事情,例如 componentDidMount ,能够在组件挂载完成后执行一些东西(这是类组件生命周期常用的写法)

在函数式组件中也可以实现,它采用的是 useEffect Hook ,它的语法更加的简单,同时融合了 componentDidUpdata 生命周期,极大的方便了我们的开发

注意点:useEffect 是在render结束之后才执行的。

js 复制代码
React.useEffect(() => {
    console.log('被调用了');
})

由于函数的特性,我们可以在函数中随意的编写函数,这里我们调用了 useEffect 函数,这个函数有多个功能

当我们像上面代码那样使用时,它相当于 componentDidUpdatacomponentDidMount 一同使用,也就是在组件挂载和组件更新的时候都会调用这个函数


它还可以接收第二个参数 ,这个参数表示它要监测的数据,也就是他要监视哪个数据的变化,分三种情况传递参数

当我们监听页面上所有的状态变化的时候,第二个参数上就可以什么都不传,当页面上有任意状态变化,useEffect就会执行

js 复制代码
React.useEffect(() => {
    console.log('被调用了');
})

当我们不需要监听任何状态变化的时候 ,我们可以就传递一个空数组 ,这样它就能当作 componentMidMount 来使用(比如一上来就加载数据这种操作),这样我们只有在组件第一次挂载的时候触发

js 复制代码
React.useEffect(() => {
    console.log('被调用了');
}, [])

当我们需要监听一个数据或多个数据变化的时候,我们也可以在数组里选择指定的数据来进行监控,当监听的数据发生变化时,相应的函数体就会被执行

js 复制代码
React.useEffect(() => {
    console.log('被调用了');
}, [被监听的变量1,被监听的变量2,被监听的变量3...])

当我们想要在卸载一个组件之前进行一些清除定时器 的操作,在类式组件中 ,我们会调用生命周期钩子 componentDidUnmount 来实现,但在函数式组件中 ,我们的写法更为简单,我们直接在 useEffect 的第一个参数传入返回值实现即可

也就是useEffect的return值,当组件卸载时,这个函数会被执行。这可以帮助我们取消或清理副作用,比如取消网络请求、关闭定时器等。如果不需要清理副作用,useEffect可以不返回任何值。

测试一下

手动实现卸载

js 复制代码
function unmount() {
    ReactDOM.unmountComponentAtNode(document.getElementById("root"))
}

卸载前输出

js 复制代码
React.useEffect(() => {
    console.log('被调用了');
    return () => {
        console.log('我要被卸载了');
    }
}, [count])

useEffect里写了return就可以实现在组件即将被卸载的时候输出

因此 useEffect 相当于三个生命周期钩子

`componentDidMount()` 组件刚挂载好就调用,第二个参数传入空数组的场景

`componentDidUpdate()` 某一个state发生变化时调用,第二个参数传入某个state来监听的场景

`componentWillUnmount()` 组件即将卸载时调用,再useEffect里写了return的场景

除此以外的就是监听普通数据的变化,所以不算在生命周期钩子里面

总结useEffect

javascript 复制代码
(1). Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
(2). React中的副作用(useEffect)操作:
        发ajax请求数据获取
        设置订阅 / 启动定时器
        手动更改真实DOM
(3). 语法和说明: 
        useEffect(() => { 
          // 在此可以执行任何带副作用操作
          return () => { // 在组件卸载前执行(可选,非必须项目)
            // 在此做一些收尾工作, 比如清除定时器/取消订阅等
          }
        }, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
(4). 可以把 useEffect Hook 看做如下三个生命周期函数的组合
	[componentDidMount()]   组件刚挂载好就调用,第二个参数传入空数组的场景
	[componentDidUpdate()]   某一个state发生变化时调用,第二个参数传入某个state来监听的场景
	[componentWillUnmount() ]	组件即将卸载时调用,再useEffect里写了return的场景

useLayoutEffect

useEffect 很类似,用的不多,一般都是用useEffect。官方也并不推荐用useLayoutEffect,会阻塞浏览器重新绘制,从而影响性能。useLayoutEffect里的操作都执行完毕之后,才会继续让浏览器往下走,所以会影响性能。

它的作用是:在 DOM 更新完成之后执行某个操作

注意:

执行时机在 useEffect 之前,其他都和 useEffect 都相同

useEffect 执行时机在 render 之后

useLayoutEffect 执行时机在 DOM 更新之后,比render还要晚一点

打印一下运行顺序

javascript 复制代码
  React.useEffect(() => {
    console.log("useEffect被调用了");
  }, []);

  React.useLayoutEffect(() => {
    console.log("useLayoutEffect被调用了");
  }, []);

总的来说不推荐用,了解即可。

useRef

当我们想要获取组件内的信息时,在类式组件中,我们会采用 ref 的方式来获取。在函数式组件中,我们可以采用也可以采用 ref 但是,我们需要采用 useRef 函数来创建一个 ref 容器,保存目标节点信息,这和 createRef 很类似。

js 复制代码
const myRef= React.useRef();
// 获取到input标签的全部信息,并且绑定给myRef上
<input type="text" ref={myRef} />
...butten触发打印

获取 ref 值,即可打印节点里的信息

js 复制代码
function show() {
    console.log(myRef)
}

即可成功的获取到 input 框中的值,包括value等一系列的目标标签属性

打印标签全部属性

总结useRef

javascript 复制代码
(1). Ref Hook可以在函数组件中存储/查找 组件内的标签或任意其它数据
(2). 语法: const refContainer = useRef()
(3). 作用:保存标签对象并随变化更新,功能与类组件的React.createRef()一样

useContext

相比于Context,useContext是为函数式组件打造的Hook

举个例子:

祖组件内容:

javascript 复制代码
// 句柄创建一次即可,规定作用范围,将需要传递的孙组件一并传入进去
export const MyContext = React.createContext();

export default function App() {
  return (
    <BrowserRouter>
      {/* 确定作用范围的同时,value属性传入参数 */}
      <MyContext.Provider value={"123456"}>
        <A />
      </MyContext.Provider>
    </BrowserRouter>
  );
}

祖孙组件通过A组件来桥接,即 App祖组件 -> A子组件 -> B孙组件

在孙组件中,通过 useContext(祖组件中定义的Context) 来获取数据

javascript 复制代码
import React from "react";
// 导入定义好的范围句柄,用于Hooks接收
import { MyContext } from "../../App";

export default function B() {
  // Hoooks接收
  const num = React.useContext(MyContext);
  return <div>B组件接收:{num}</div>;
}

useMemo

待更新

相关推荐
Pedantic1 小时前
SwiftUI 手势层级(Gesture Hierarchy)详解
前端
飘尘1 小时前
前端转型全栈(Java后端)的快速上手指引
前端·后端·全栈
一颗烂土豆1 小时前
Meshopt 压缩深度解析,为什么它比 Draco 更快
前端·javascript·webgl
YFF菲菲兔2 小时前
调度系统和调和系统的桥梁
react.js
浏览器工程师2 小时前
AI Agent 接浏览器任务,先别让它一路点到底
前端·后端
雨季mo浅忆2 小时前
VSCode自动格式化三要素
前端
爱勇宝3 小时前
深扒 Anthropic 1680 位工程师简历:应届生几乎没机会,AI 公司最缺的不是博士
前端·后端·程序员
kyriewen4 小时前
同事每天催我 Code Review,我写了个脚本让 AI 替我 review PR——现在他反过来催 AI 了
前端·javascript·ai编程
user20585561518136 小时前
Windows 项目安装时报 `node-sass` 错误,如何快速处理
前端