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

待更新

相关推荐
Json_1817901448030 分钟前
电商拍立淘按图搜索API接口系列,文档说明参考
前端·数据库
风尚云网1 小时前
风尚云网前端学习:一个简易前端新手友好的HTML5页面布局与样式设计
前端·css·学习·html·html5·风尚云网
木子02041 小时前
前端VUE项目启动方式
前端·javascript·vue.js
GISer_Jing1 小时前
React核心功能详解(一)
前端·react.js·前端框架
捂月1 小时前
Spring Boot 深度解析:快速构建高效、现代化的 Web 应用程序
前端·spring boot·后端
深度混淆1 小时前
实用功能,觊觎(Edge)浏览器的内置截(长)图功能
前端·edge
Smartdaili China1 小时前
如何在 Microsoft Edge 中设置代理: 快速而简单的方法
前端·爬虫·安全·microsoft·edge·社交·动态住宅代理
秦老师Q1 小时前
「Chromeg谷歌浏览器/Edge浏览器」篡改猴Tempermongkey插件的安装与使用
前端·chrome·edge
滴水可藏海1 小时前
Chrome离线安装包下载
前端·chrome
m51271 小时前
LinuxC语言
java·服务器·前端