React复习

news2024/11/24 19:36:21

文章目录

    • 常用的Hooks
      • useState
      • useReducer
      • useRef
      • useContext
      • useMemo
      • useCallback
      • useEffect
    • 组件通信
      • Props(属性)
      • Ref(引用)
      • Context(上下文)
      • State(状态)
      • 回调函数
      • Event Bus(事件总线)
      • Custom Hooks(自定义钩子)
      • Redux
    • 生命周期
      • React 17 之前:
      • React 17 之后:
      • 常用的生命周期方法:
        • Class 组件中常用:
        • Function 组件中使用(Hooks):
      • 不常用的生命周期方法:
        • Class 组件中不常用:
        • Class 组件中特有的:

好多年没使react了,有些忘了,简单复习一下…

常用的Hooks

useState

用于管理组件内部的状态,允许在函数组件中添加和管理 state。

import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}

useReducer

管理复杂的状态逻辑,特别是当 state 逻辑涉及到多个子值,或者下一个 state 依赖于前一个 state 时。

import React, { useReducer } from 'react';

const initialState = { count: 0 };
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      return state;
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
    </>
  );
}

useRef

在组件中直接访问DOM 元素;

import React, { useRef } from 'react';

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` 指向挂载的文本输入元素
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

在组件的不同渲染之间保持一个可变的值,并且这个值不会触发组件的重新渲染。

import React, { useRef, useState, useEffect } from 'react';

function Timer() {
  const intervalRef = useRef();
  const [count, setCount] = useState(0);

  useEffect(() => {
    intervalRef.current = setInterval(() => {
      setCount((c) => c + 1);
    }, 1000);
    return () => clearInterval(intervalRef.current);
  }, []);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => clearInterval(intervalRef.current)}>Stop Timer</button>
    </div>
  );
}

useContext

在组件之间共享状态,而不必通过 “props下钻” 层层传递。

import React, { useContext } from 'react';
const ThemeContext = React.createContext('light');

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return <button theme={theme}>I am styled by theme context!</button>;
}

useMemo

缓存计算密集型函数的返回值。
当你把一些计算开销较大的值或者组件作为依赖项传递给其他组件或者渲染逻辑时,使用 useMemo 可以避免不必要的计算,从而提高性能。

import React, { useMemo } from 'react';

function expensiveCalculation(num) {
  console.log('Calculating...');
  for (let i = 0; i < 1000000; i++) {} // 模拟一个耗时计算
  return num * 2;
}

function MyComponent({ a, b }) {
  const memoizedValue = useMemo(() => {
    return expensiveCalculation(a);
  }, [a]); // 只有 a 改变时,才会重新计算

  return (
    <div>
      {memoizedValue}
      <p>Dependency: {b}</p>
    </div>
  );
}


useCallback

缓存函数。
当你将一个函数传递给子组件或者作为 props 传递时,使用 useCallback 可以避免不必要的渲染,从而提高性能。

import React, { useCallback, useState } from 'react';

function MyComponent() {
  const [count, setCount] = useState(0);

  const handleIncrement = useCallback(() => {
    setCount((c) => c + 1);
  }, []); // 由于 setCount 是稳定的,所以这里不需要依赖项

  const handleDecrement = useCallback(() => {
    setCount((c) => c - 1);
  }, []); // 同上

  return (
    <div>
      <button onClick={handleIncrement}>Increment</button>
      <button onClick={handleDecrement}>Decrement</button>
      <p>Count: {count}</p>
    </div>
  );
}

useEffect

允许在函数组件中执行副作用操作,如数据获取、订阅或手动更改 React 组件中的 DOM。

import React, { useState, useEffect } from 'react';

function ExampleComponent() {
  useEffect(() => {
    // 这个函数会在组件每次渲染后执行
    // 可以在这里执行副作用操作
    return () => {
      // 这个清理函数会在组件卸载前执行
    };
  }, []); // 空数组表示这个 effect 只会在组件挂载时执行一次
  return <div>Hello, world!</div>;
}


组件通信

Props(属性)

最常见的方式是通过props属性将数据从父组件传递给子组件。父组件可以在子组件的标签中传递props,子组件通过props接收数据。

// ParentComponent.js
import ChildComponent from './ChildComponent';

function ParentComponent() {
  const data = 'Hello from parent';
  
  return <ChildComponent data={data} />;
}

// ChildComponent.js
function ChildComponent(props) {
  return <div>{props.data}</div>;
}

Ref(引用)

可以使用ref来直接访问子组件的实例,并通过实例方法传递数据。

// ParentComponent.js
import React, { useRef } from 'react';
import ChildComponent from './ChildComponent';

function ParentComponent() {
  const childRef = useRef();

  const sendDataToChild = () => {
    childRef.current.handleData('Hello from parent');
  };

  return (
    <>
      <ChildComponent ref={childRef} />
      <button onClick={sendDataToChild}>Send Data</button>
    </>
  );
}

// ChildComponent.js
import React, { forwardRef, useImperativeHandle } from 'react';

const ChildComponent = forwardRef((props, ref) => {
  const handleData = (data) => {
    console.log(data);
  };

  useImperativeHandle(ref, () => ({
    handleData
  }));

  return <div>Child Component</div>;
});

export default ChildComponent;

Context(上下文)

Context允许在组件树中传递数据,而不必一级一级手动传递props。适用于跨多层级的组件传递数据。

// DataContext.js
import React from 'react';

const DataContext = React.createContext();

export default DataContext;

// ParentComponent.js
import DataContext from './DataContext';
import ChildComponent from './ChildComponent';

function ParentComponent() {
  const data = 'Hello from parent';
  
  return (
    <DataContext.Provider value={data}>
      <ChildComponent />
    </DataContext.Provider>
  );
}

// ChildComponent.js
import DataContext from './DataContext';

function ChildComponent() {
  return (
    <DataContext.Consumer>
      {data => <div>{data}</div>}
    </DataContext.Consumer>
  );
}

State(状态)

通过组件的状态来传递数据。父组件可以将数据存储在自身的状态中,然后通过props将数据传递给子组件。

// ParentComponent.js
import ChildComponent from './ChildComponent';

function ParentComponent() {
  const [data, setData] = useState('Hello from parent');
  
  return <ChildComponent data={data} />;
}

// ChildComponent.js
function ChildComponent(props) {
  return <div>{props.data}</div>;
}

回调函数

父组件可以通过回调函数将数据传递给子组件。子组件可以调用回调函数来传递数据给父组件。

// ParentComponent.js
import ChildComponent from './ChildComponent';

function ParentComponent() {
  const handleData = (data) => {
    console.log(data);
  };
  
  return <ChildComponent sendData={handleData} />;
}

// ChildComponent.js
function ChildComponent(props) {
  const sendDataToParent = () => {
    props.sendData('Hello from child');
  };
  
  return <button onClick={sendDataToParent}>Send Data</button>;
}

Event Bus(事件总线)

通过事件总线来进行组件间的通信,一个组件发送事件,另一个组件监听事件并做出相应处理。

// EventBus.js
import { EventEmitter } from 'events';

const eventBus = new EventEmitter();

export default eventBus;

// ParentComponent.js
import eventBus from './EventBus';
import ChildComponent from './ChildComponent';

function ParentComponent() {
  const sendDataToChild = () => {
    eventBus.emit('sendData', 'Hello from parent');
  };

  return (
    <>
      <ChildComponent />
      <button onClick={sendDataToChild}>Send Data</button>
    </>
  );
}

// ChildComponent.js
import React, { useEffect } from 'react';
import eventBus from './EventBus';

function ChildComponent() {
  useEffect(() => {
    eventBus.on('sendData', (data) => {
      console.log(data);
    });

    return () => {
      eventBus.off('sendData');
    };
  }, []);

  return <div>Child Component</div>;
}

Custom Hooks(自定义钩子)

通过自定义钩子来共享逻辑和状态,并在不同组件中使用。可以将数据存储在自定义钩子中,然后在需要的组件中使用。

// UseData.js
import { useState } from 'react';

const useData = () => {
  const [data, setData] = useState('Hello from custom hook');

  return { data, setData };
};

export default useData;

// ParentComponent.js
import useData from './UseData';
import ChildComponent from './ChildComponent';

function ParentComponent() {
  const { data } = useData();

  return <ChildComponent data={data} />;
}

// ChildComponent.js
function ChildComponent(props) {
  return <div>{props.data}</div>;
}


Redux

在React中结合Redux进行状态管理,通常需要使用react-redux库来连接Redux Store和React组件。

  • 创建Redux Store:
// store.js
import { createStore } from 'redux';
import rootReducer from './reducers';

const store = createStore(rootReducer);

export default store;

  • 定义Reducer:
// reducers.js
const initialState = {
  count: 0
};

const counterReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + 1 };
    case 'DECREMENT':
      return { ...state, count: state.count - 1 };
    default:
      return state;
  }
};

export default counterReducer;

  • 连接Redux Store和React组件:
// App.js
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';

const App = () => {
  const count = useSelector(state => state.count);
  const dispatch = useDispatch();

  const increment = () => {
    dispatch({ type: 'INCREMENT' });
  };

  const decrement = () => {
    dispatch({ type: 'DECREMENT' });
  };

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </div>
  );
};

export default App;

  • Provider组件:在根组件中使用Provider组件将Redux Store传递给整个应用程序。
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

状态管理还有很多其他的方式(特别卷)。比如: Zustand、MobX等等


生命周期

挂载(Mounting)、更新(Updating)和卸载(Unmounting)的过程。

React 17 之前:

挂载阶段(Mounting)

  • constructor(): 构造函数,在组件被创建时调用,用于初始化state、可以接收一个父组件传来的props、绑定事件处理方法。
  • componentWillMount(已弃用): 组件将要挂载
  • render(): 在这里面我们会写一些html标签及自定义的函数,render执行完后便会将这些语句对应渲染到浏览器上面。
  • componentDidMount(): 组件挂载后调用,通常用于进行异步数据获取、订阅事件等操作。

更新阶段(Updating)

  • componentWillReceiveProps(已弃用): 在组件接收到新的 props 之前调用。
  • shouldComponentUpdate(): 决定是否重新渲染组件,在接收新props或state时调用,用于性能优化。 当更新state值的时候会执行这个函数,比如this.setState({})。
  • componentWillUpdate(已弃用): 会在组件接收到新的 props 或 state 并即将重新渲染之前被调用。
  • render(): 和初始化时候执行的那个render一样,只是这里是更新值的,所以dom节点会重新更新一下。
  • componentDidUpdate(): 组件更新后调用,通常用于处理DOM操作、网络请求等。

卸载阶段(Unmounting)

  • componentWillUnmount(): 组件卸载前调用,用于清理定时器、取消订阅等操作。

React 17 之后:

挂载阶段(Mounting)

  • constructor()
  • static getDerivedStateFromProps: React 会在初始挂载和后续更新时调用 render 之前调用它。它应该返回一个对象来更新 state,或者返回 null 就不更新任何内容。挂载阶段只调用一次,用于初始化 state。
  • render()
  • componentDidMount()

更新阶段(Updating)

  • static getDerivedStateFromProps: 更新阶段每次组件接收到新的 props 时都会被调用。
  • shouldComponentUpdate()
  • render()
  • getSnapshotBeforeUpdate: 会在 React 更新 DOM 之前时直接调用它。它使你的组件能够在 DOM 发生更改之前捕获一些信息(例如滚动的位置)。此生命周期方法返回的任何值都将作为参数传递给 componentDidUpdate。
  • componentDidUpdate()

卸载阶段(Unmounting)

  • componentWillUnmount()

在这里插入图片描述

除了上述生命周期方法,还有componentDidCatch()用于捕获组件树中的错误。此外,React Hooks也提供了函数式组件中的生命周期替代方案,如useEffect()用于处理副作用。

  • componentDidCatch:
import React, { Component } from 'react';

class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  componentDidCatch(error, info) {
    this.setState({ hasError: true });
    console.error('Error caught by ErrorBoundary:', error, info);
  }

  render() {
    if (this.state.hasError) {
      return <h1>Something went wrong.</h1>;
    }
    return this.props.children;
  }
}

export default ErrorBoundary;

  • useEffect: 在函数式组件中,可以使用React Hooks来替代类组件中的生命周期方法。
import { useState, useEffect } from 'react';

const FunctionalComponent = () => {
  const [count, setCount] = useState(0);

  // 相当于 componentDidMount 和 componentDidUpdate
  useEffect(() => {
    console.log('Component is mounted or updated');
    
    // 清理函数,相当于 componentWillUnmount
    return () => {
      console.log('Component is about to unmount');
    };
  }, [count]); // 仅在count发生变化时触发

  // 模拟 shouldComponentUpdate
  const shouldUpdate = (nextProps, nextState) => {
    if (nextState.count !== count) {
      return true;
    }
    return false;
  };

  const handleClick = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>Increment Count</button>
    </div>
  );
};

export default FunctionalComponent;


常用的生命周期方法:

在这里插入图片描述

Class 组件中常用:
  • constructor(): 用于初始化 state 和绑定方法。
  • render(): 必须的方法,用于渲染组件。
  • componentDidMount(): 在组件挂载后被调用,常用于进行 API 调用、订阅等。
  • componentDidUpdate(prevProps, prevState): 在组件更新后被调用,可以用于比较 props 和 state 的变化。
  • componentWillUnmount(): 在组件卸载前被调用,常用于清理资源,如取消订阅、清除定时器等。
Function 组件中使用(Hooks):
  • useEffect(): 用于在函数组件中执行副作用操作,类似于 componentDidMount、componentDidUpdate 和 componentWillUnmount。
  • useLayoutEffect(): 类似于 useEffect,但它会在所有的 DOM 变更之后同步调用 effect,适用于读取 DOM 布局并同步触发重渲染的情况。

不常用的生命周期方法:

Class 组件中不常用:
  • UNSAFE_componentWillMount(): 虽然可用,但官方不推荐使用,其用途通常可以在 componentDidMount 中实现。
  • UNSAFE_componentWillReceiveProps(nextProps): 虽然可用,但官方不推荐使用,推荐使用 getDerivedStateFromProps 或在 componentDidUpdate 中处理新的 props。
  • UNSAFE_componentWillUpdate(nextProps, nextState): 虽然可用,但官方不推荐使用,其用途通常可以在 componentDidUpdate 中实现。
Class 组件中特有的:
  • getDerivedStateFromProps(props, state): 这是一个静态方法,用于根据 props 更新 state,在组件挂载和更新时都会被调用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2208990.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python WebSocket 的原理及其应用

Python WebSocket 的原理及其应用 在现代 Web 开发中&#xff0c;实时通信成为了越来越多应用的重要组成部分。尤其是像聊天应用、实时数据更新、在线游戏等场景&#xff0c;服务器与客户端之间的即时数据传输需求非常迫切。在传统的 HTTP 协议中&#xff0c;通信往往是基于请…

在docker的容器内如何查看Ubuntu系统版本

文章目录 写在前面一、问题描述二、解决方法参考链接 写在前面 自己的测试环境&#xff1a; docker 一、问题描述 由于 lsb_release -a 只能查看自己电脑&#xff08;宿主机&#xff09;的系统版本&#xff0c;如果在docker的容器内又应该如何查看Ubuntu系统版本呢&#xff…

GoPro 解决方案:恢复 GoPro 数据、GoPro 重置为出厂设置

在本文中&#xff0c;我们将向您展示如何轻松将 GoPro 相机重置为出厂设置以及如何从已重置为出厂设置的 GoPro 中恢复丢失的数据。 第 1 部分&#xff1a;将 GoPro 重置为出厂设置后恢复丢失的数据。 ​在将 GoPro 重置为出厂设置之前&#xff0c;最好对视频进行完整备份。但…

URDF统一机器人建模语言

统一机器人建模语言 URDF&#xff08;Unified Robot Description Format&#xff09;统一机器人描述格式&#xff0c;URDF使用XML格式描述机器人文件。 我们从下面四个方面介绍URDF&#xff1a; URDF的组成介绍 URDF-Link介绍 URDF-Joint介绍 创建一个简单的URDF…

大数据新视界 --大数据大厂之差分隐私技术在大数据隐私保护中的实践

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

ribbon和nginx负载均衡图解

通俗来说 nginx&#xff1a; 规定一个地址v&#xff08;比如v代理了地址a,b,c,d且他们都实现了同一个服务e&#xff09;&#xff0c;然后当我们的请求想要实现e服务而去请求v的时候&#xff0c;v实际上就会从a,b,c,d中选一个来让他们给请求者提供服务。 ribbon&#xff1a; …

[Halcon矩阵] 通过手眼标定矩阵计算相机旋转角度

&#x1f4e2;博客主页&#xff1a;https://loewen.blog.csdn.net&#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;本文由 丶布布原创&#xff0c;首发于 CSDN&#xff0c;转载注明出处&#x1f649;&#x1f4e2;现…

idear2024-Springcloud项目一个服务创建多个实例/端口

国庆重装系统&#xff0c;安装了最新版的idear,结果带来一堆bug。 解决办法&#xff1a; 初始配置&#xff1a; 初始状态&#xff1a; 1.点击右上角3个点&#xff0c;再点击编辑 2.点击修改选项 3.点击允许多个实例 可以发现下面多了个选项 点击&#xff1a;应用-》确定 4.修…

windows自动化(一)---windows关闭熄屏和屏保

电脑设置关闭屏幕和休眠时间不起作用解决方案 一共三个方面注意&#xff1a; 一、关闭屏保设置&#xff1a; 二、电源管理设置 三、关闭盖子不做操作&#xff1a; 第一点很重要&#xff0c;就算二三都做了&#xff0c;一没做&#xff0c;照样不行。

win软件 超强的本地视频 图片去水印 动态水印!

AI视频图片去水印 HitPaw Watermark Remover 电脑软件&#xff0c;内涵安装教程&#xff0c;以后看到有水印的视频不怕啦&#xff0c;用这个就行了&#xff0c;可以去除动态水印&#xff01; 【下载】 https://pan.quark.cn/s/1ba6f088f0b2 【应用名称】:HitPaw Watermark R…

[Linux] Linux 进程程序替换

标题&#xff1a;[Linux] Linux 进程程序替换 个人主页水墨不写bug &#xff08;图片来源于网络&#xff09; 目录 O、前言 一、进程程序替换的直观现象&#xff08;什么是进程程序替换&#xff1f;&#xff09; 二、进程程序替换的原理 三、进程程序替换的函数&#xff08…

软件游戏d3dx9_43.dll丢失怎么解决,总结6个解决方法

d3dx9_43.dll是DirectX 9组件的一部分&#xff0c;这是一个由微软开发的图形API&#xff0c;用于处理与游戏和多媒体相关的图形渲染。d3dx9_43.dll库包含了DirectX 9中用于3D图形渲染和处理的许多实用程序函数。这些函数为开发者提供了创建复杂3D模型、纹理映射、光影效果以及各…

涂鸦革新WebRTC技术!让IPC监测低延时、高可靠更安全

随着科技的飞速发展&#xff0c;越来越多人开始关注居家安全、食品安全、校园安全等领域&#xff0c;大家对实时监测的需求也在不断升级。想象一下&#xff0c;无论身处何地&#xff0c;只需轻触屏幕&#xff0c;就能实时查看家中、办公室或任何你关心的地方&#xff0c;这不再…

MySQL中表的操作

目录 一、查看所有表 1.1、语法 二、创建表 2.1、语法 2.2、示例&#xff1a; 2.3、创建数据加时使⽤校验语句[if not exists] 三、查看表结构 3.1、语法 3.2、示例 四、删除表 4.1、语法 4.2、示例 4.3、注意事项 五、主要数据类型 5.1、数值类型 5.2、日期和…

揭秘语音识别巨头1:国内外顶尖技术服务商全解析01(万字长文)

一、学习导航 解密语音识别巨头&#xff1a;国内顶尖技术服务商全解析00&#xff1a;学习地图 解密语音识别巨头&#xff1a;国内顶尖技术服务商全解析01&#xff1a;微软语音&#xff0c;商业No.1 解密语音识别巨头&#xff1a;国内顶尖技术服务商全解析02&#xff1a;百度…

ProxyPin 抓包,原来可以这么简单!

你是否还在为网络请求的抓包发愁&#xff1f;其实&#xff0c;ProxyPin 可以让抓包操作变得异常简单&#xff01;不需要复杂的设置&#xff0c;也不用繁琐的配置&#xff0c;轻松几步就能实现。让我们一起来看看吧&#xff01; 抓包操作常用于测试网络请求、分析接口响应&#…

Javascript剩余参数、arguments对象和柯里化函数

在JavaScript中&#xff0c;函数的剩余参数&#xff08;Rest Parameters&#xff09;和arguments对象都是用于处理函数接收的不定数量参数的机制。虽然它们的功能相似&#xff0c;但使用方式和适用场景有所不同。下面详细解释这两个概念。 剩余参数&#xff08;Rest Parameter…

手撕数据结构 —— 栈(C语言讲解)

目录 1.认识栈 什么是栈 栈的示意图 2.如何实现栈 3.栈的实现 Stack.h中接口总览 具体实现 结构的定义 初始化栈 销毁栈 入栈 出栈 取栈顶元素 获取有效元素的个数 判断栈是否为空 4.完整代码附录 Stack.h Stack.c 1.认识栈 什么是栈 栈是一种特殊的线性表…

【动物识别系统】Python+卷积神经网络算法+人工智能+深度学习+机器学习+计算机课设项目+Django网页界面

一、介绍 动物识别系统。本项目以Python作为主要编程语言&#xff0c;并基于TensorFlow搭建ResNet50卷积神经网络算法模型&#xff0c;通过收集4种常见的动物图像数据集&#xff08;猫、狗、鸡、马&#xff09;然后进行模型训练&#xff0c;得到一个识别精度较高的模型文件&am…

DS线性表之单链表的讲解和实现(2)

文章目录 前言一、链表的概念二、链表的分类三、链表的结构四、前置知识准备五、单链表的模拟实现定义头节点初始化单链表销毁单链表打印单链表申请节点头插数据尾插数据头删数据尾删数据查询数据在pos位置之后插入数据删除pos位置之后的数据 总结 前言 本篇的单链表完全来说是…