一文搞清楚React技术栈关键核心内容

news2024/9/22 21:06:13

一、类组件与函数式组件

1、类组件

遵循es6类的写法,该类必须继承React.component

通过props访问父组件

继承一个类使用extends,子类中constructor内使用super()继承父类属性

组件必须实现render方法,return一个react组件

2、函数式组件

使用hooks的useState()管理状态

useEffect()实现生命周期componentDidMount()和componentWillUnmount()

二、React事件机制

1、React有自己的一套事件机制,称为合成事件,如onClick

onClick没有绑定在具体的dom上,而是绑定在结构最外层document对象上,使用统一的事件去监听。

事件监听器通过一个映射保存所有的事件监听和处理函数,当组件挂载和卸载时,事件监听器上插入或删除一些对象

2、执行顺序

先执行真实dom元素触发的事件,再执行React事件,最后执行document上挂载的事件

三、组件间数据传递

1、父向子传递:props

2、子向父传递:父组件向子组件传递一个函数,父组件通过这个函数的回调,拿到子组件传过来的值

3、兄弟组件之间传递:可以把父组件作为媒介中间传递

4、父组件向后代传递:createContext(), 使用Provider包裹组件,后代组件通过useContext()获取

5、无关系之间组件传值,使用Redux

四、高阶组件

function HOC(WrappedComponent) {
	const newProps = {type: 'HOC'};
	return props => <WrappedComponent {...props} {...newProps}></WrappedComponent>
}

1、用途

高阶函数常用于与核心业务无关的功能,如日志记录、权限控制、数据校验、异常处理、统计上报等

2、遵循的原则

  1. props保持一致
  2. 不能在函数组件上使用ref属性,因为没有实例
  3. 不要改变原始组件
  4. 不要在render方法中使用高阶组件
  5. 包装显示名字便于调用

3、使用方法

  1. 属性代理,操作props,或者加一些条件渲染拦截器
  2. 反向继承,操作组件内的state和生命周期

五、react组件间过渡动画实现

1、CSSTransition

  1. in属性设置为true时,会添加两个class:xxx-enter、xxx-enter-active,动画移除完成后移除这两个类,添加-enter-done类
  2. in属性设置为false时,会添加两个class:xxx-exit、xxx-exit-active,动画移除完成后移除这两个类,添加-exit-done类

所以可以实现两个状态的平滑过渡

<div className={'container'}>
<div className={'square-wrapper'}>
<CSSTransition 
	in={show} 
	timeout={500} 
	classNames={'fade'} 
	unmountOnExit={true}
>
	<div className={'square'} />
</CSSTransition>
</div>
.fade-enter {
opacity: 0;
transform: translateX(100%);
}
.fade-enter-active { 
opacity: 1;
transform: translateX(0); 
transition: all 500ms;
}
.fade-exit {
opacity: 1;
transform: translateX(0);
}
.fade-exit-active {
opacity: 0;
transform: translateX(-100%); transition: all 500ms;
}

2、SwitchTransition

  1. mode属性:

    in-out: 新组件先进入,旧组件再移除

    out-in:旧组件先移除,新组件再进入

  2. SwitchTransition组件内必须包裹CSSTransition,不能直接包裹组件

  3. 用于实现炫酷的组件切换

render() {
	const {isOn} = this.state;
   return (
		<SwitchTransition mode="out-in">
			<CSSTransition 
				classNames="btn" 
				timeout={500} 
				key={isOn ? "on" : "off"}>
				{
					<button onClick={this.btnClick.bind(this)}> 
						{isOn ? "on": "off"}
					</button>
				}
             </CSSTransition>
         </SwitchTransition>
       )
     }

3、TransitionGroup

  1. 有一组动画时使用TransitionGroup包裹CSSTransition,同样CSSTransition组件内不用in属性,使用key属性
  2. 当TransitionGroup感知到child变化时先保存移除的节点,当动画结束时再真正移除

处理方式:

  • 插入的节点,先渲染dom,再做动画
  • 删除的节点,先做动画,再删除dom

六、如何捕获处理错误异常

1、react16引用了错误边界

可以捕获子组件数任何位置发生的js错误

使用方式分为类组件和函数式组件

  1. 类组件中使用static getDerivedStateFromError()渲染备用ui,使用componentDidCatch()打印错误信息
  2. 函数式组件在useEffect中,使用window.addEventListener(‘error’, logError);捕获异常
// 创建一个错误边界组件
const ErrorBoundary = ({ children }) => {
  const [hasError, setHasError] = useState(false);

  useEffect(() => {
    const logError = (error, info) => {
      console.error('Error:', error);
      console.error('Info:', info);
      setHasError(true);
    };
    window.addEventListener('error', logError);
    return () => {
      window.removeEventListener('error', logError);
    };
  }, []);

  if (hasError) {
    return <div>Something went wrong.</div>;
  }

  return children;
};

无法捕获的异常:

  • 事件处理
  • 异步代码
  • 服务器渲染
  • 自身抛出来的错误

对于错误边界无法捕获的异常,如事件处理过程中的问题,因为不在渲染时触发,这一类可以通过try…catch…方法捕获

七、setState

1、状态更新

  1. 在组件生命周期中和合成事件中是异步
  2. 在setTimeout和原生事件中是同步
  3. 同一个值多次执行setState,批量更新策略会进行覆盖,取最后一次执行结果

因此下面的代码只执行最后的那次

handleClick = ()=>{
	this.setState({count: this.state.count + 1})
	this.setState({count: this.state.count + 1})
	this.setState({count: this.state.count + 1})
}

可以使用prevState回调参数,使用 prevState 来表示前一个状态值,确保状态更新是基于前一个状态进行的,而不是基于当前组件中的局部变量,这样可以避免由于异步更新导致的状态不一致问题。

handleClick = ()=>{
	this.setState((prevState) => ({
      count: prevState.count + 1,
    }));
	this.setState((prevState) => ({
      count: prevState.count + 1,
    }));
	this.setState((prevState) => ({
      count: prevState.count + 1,
    }));
}

2、render

  • 在类组件中,使用setState,一定会造成组件的render
  • 函数式组件中,使用useState, 不一定造成组件重新渲染,useState值不变时不会改变

八、虚拟dom

1、virtual dom树的节点分为:

  • type 标签
  • attributes:标签属性
  • children:子节点

2、jsx把virtual dom解析为dom

  1. jsx通过babel最终转化为React.createElement
  2. 转化过程中,babel会判断jsx中组件首字母,大写被认定为自定义组件,小写被解析成字符串
  3. React.createElement被调用时,传入标签类型type、标签属性props及若干子元素children
  4. 通过ReactDOM.render进行渲染成真实DOM
  5. 首次调用时容器节点都被替换,后续的调用通过React的diff算法进行高效更新
  6. 如果提供了回调函数,callback会在组件被渲染更新后执行

九、Fiber架构

1、问题

js引擎和页面渲染两个是互斥的,一个线程执行时另一个挂起等待,如果js线程长时间占用主线程,用户体验就会变差

在React15版本中,reconciler的mountComponent和updateComponent,是递归的去更新子组件的,层级深递归时间长就会卡顿。

且React15是同步更新,不支持异步可中断的更新。

2、Fiber是什么

Fiber是React16才有的,是React内部实现的数据结构,是Fiber树结构的节点单位。

支持状态更新、优先级调度、异步可中断。

  1. 增加了异步任务,实现了功能完备的requestIdleCallbackpolyfill,也就是Scheduler,浏览器空闲时执行。
  2. Scheduler,调度任务优先级,优先级高的可以中断优先级低的,然后再重新执行优先级低的任务。
  3. dom diff树变成了链表结构,一个dom对应两个fiber(一个链表),对应两个队列,这都是为了找到被中断的任务,重新执行。

Fiber数据结构包含的主要参数:

  1. 基本参数:tag、key、type
  2. 节点数的指向:return、child、sibling、index
  3. 用于单链表查询的:firstEffect、nextEffect、lastEffect
  4. 功能性:expirationTime、alternate

3、如何解决

  1. Fiber把渲染过程拆分成多个子任务,每次只做一部分,看是否有剩余时间,如果没有时间就挂起,等主线程不忙时再执行
  2. 中断可恢复,恢复后可继续复用之前的中间状态,给不同的任务赋予不同优先级,每个任务更新单元是React Element对应的Fiber节点
  3. 使用双缓存更新Dom,同时保存两颗Fiber树,当前屏幕上对应的Fiber树是Current Fiber, 正在内存构建的是workInPregress Fiber,两者通alternate连接
  4. 作为静态数据结构来说,每个Fiber节点对应一个React element,保存该节点的类型、对应dom节点信息
  5. 作为动态数据结构来说,每个Fiber保存本地更新中该组件更新的状态、要执行的工作

十、React中的key

  1. key应该是唯一的
  2. key不要随机,随机值在下次render时,会生成另一个数字
  3. 使用index作为key值,对性能优化没有作用
  4. 列表中要使用key,删除和增加元素,可有效的复用组件
  5. 只是单纯的文本内容改变,不写key反而效率更高

十一、React diff的原理

React的diff算法是通过对比新旧virtual dom找出真正的dom变化之处。

React中的diff算法主要遵循三个层级的策略:

  • tree层级
  • component层级
  • element层级

1、tree层级

  • DOM节点对跨层级的操作不做优化,只会对同层级优化
  • 只做删除、新增,不做移动

2、component层级

  • 如果type类型相同,继续往下diff,不然直接删除,创建新组件

3、element层级

对于比较同一层节点,每个节点用唯一的key标识,有三种节点操作:插入、移动、删除

十二、React Router

React Router的路由原理是,可以实现无刷新的情况下切换页面,路由的本质是页面URL发生改变,页面显示结果可以根据URL变化而变化,但是页面不会刷新

  • HashRouter, hash改变后,触发window上注册的window.hasChange事件监听URL变化,改变DOM模拟页面跳转
  • BrowserRouter,通过props传入path与context传入的pathname匹配,决定是否跳转页面

1、组成

  • react-router:核心功能
  • react-router-dom:基于react-router加入了浏览器运行环境下的一些功能
  • react-router-native:基于react-router加入了react-native一些功能
  • react-router-config:用于配置静态路由的工具库

2、常用API

  • BrowserRouter、HashRouter
  • Route
  • Link、NavLink
  • Switch
  • redirect

2.1 BrowserRouter、HashRouter

Router中包含了对路径改变的监听,并且会将对应的路径传递给子组件

  • BrowserRouter是history模式
  • HashRouter是hash模式

通过两者作为最顶层组件包裹其他组件

2.2 Route

Route用户路由的匹配,然后进行组件的渲染,对应的属性如下:

  • path属性:设置匹配的路径
  • component属性:设置匹配到了路径后渲染的组件
  • render属性:设置匹配到了路径后渲染的内容
  • exact属性:开启精准匹配,只有匹配到完全一致的路径,才会渲染组件

新版本如6.4版本开始增加了data-router

  • loader是在路由切换前获取数据,在组件内通过
import React from 'react';
import { createBrowserRouter, RouterProvider, useLoaderData } from 'react-router-dom';

const fetchData = async () => {
  return { message: 'Loaded data from loader' };
};

const HomePage = () => {
  const data = useLoaderData();
  return <div>{data? data.message : 'Loading...'}</div>;
};

const router = createBrowserRouter([
  {
    path: '/',
    element: <HomePage />,
    loader: fetchData,
  },
]);

const App = () => {
  return <RouterProvider router={router} />;
};

export default App;
  • action 常用于表单数据处理,更侧重于处理交互操作后的结果数据,使用useActionData获取action数据
import React from 'react';
import { createBrowserRouter, RouterProvider, useActionData } from 'react-router-dom';

const handleFormSubmit = async ({ request }) => {
  const formData = await request.formData();
  // 处理表单数据并返回结果
  return { submittedData: formData };
};

const FormPage = () => {
  const data = useActionData();
  return (
    <div>
      {data? (
        <div>Submitted data: {JSON.stringify(data.submittedData)}</div>
      ) : (
        <form method="post">
          <input type="text" name="field1" />
          <button type="submit">Submit</button>
        </form>
      )}
    </div>
  );
};

const router = createBrowserRouter([
  {
    path: '/form',
    element: <FormPage />,
    action: handleFormSubmit,
  },
]);

const App = () => {
  return <RouterProvider router={router} />;
};

export default App;

2.3 Link、NavLink

通常路径的跳转使用Link组件,最终被渲染成a元素,其中to代替a标题的href属性

NavLink是在Link中添加一些样式处理的方法,例如组件被选中时,发生样式变化,有以下属性:

  • activeStyle:活动时匹配的样式
  • activeClassName:活动时添加的class

另外

如果想用js处理路由跳转,可以使用useNavigate

十三、Redux

以下是关于 React Redux 的详细介绍:

1、文件结构

  1. actions 文件夹:
    • 存放定义的各种 action。
    • 例如:actionCreators.js,用于创建和返回具体的 action 对象。
  2. reducers 文件夹:
    • 包含各个 reducer 函数。
    • 每个 reducer 负责管理应用状态的一部分。
  3. store 文件夹:
    • index.js 文件通常用于创建和配置 Redux store。

2、每个部分作用

  1. Actions

    • 作用:是把数据从应用传到 store 的有效载荷。它是 store 唯一的信息来源。
    • 例如,一个用于添加待办事项的 action 可能如下:
      export const addTodo = (text) => ({
        type: 'ADD_TODO',
        payload: text,
      });
      
    • 解释:这里的 addTodo 函数创建了一个 action 对象,type 字段表示 action 的类型,payload 字段通常携带一些数据。
  2. Reducers

    • 作用:根据 action 来更新应用的状态。
    • 例如:
      const initialState = {
        todos: [],
      };
      
      const todoReducer = (state = initialState, action) => {
        switch (action.type) {
          case 'ADD_TODO':
            return {
             ...state,
              todos: [...state.todos, action.payload],
            };
          default:
            return state;
        }
      };
      
    • 解释:这个 reducer 函数接收当前状态和一个 action,根据 action 的类型来决定如何更新状态。如果是 ADD_TODO 类型的 action,就把新的待办事项添加到 todos 数组中。
  3. Store

    作用:存储应用的状态,并提供一些方法来获取状态、派发 action 和订阅状态的变化。

    • 例如:
      import { createStore } from 'redux';
      import todoReducer from './reducers/todoReducer';
      
      const store = createStore(todoReducer);
      
      export default store;
      
      解释:这里使用 createStore 函数创建了一个 Redux store,传入了 todoReducer 作为参数来指定初始状态和如何更新状态。

3、实际用法代码实例

以下是一个完整的 React + Redux 的示例:

  1. 首先,安装必要的库:

    npm install react-redux redux

  2. 创建一个简单的 React 组件:

    import React from 'react';
    import { connect } from 'react-redux';
    import { addTodo } from './actions/actionCreators';
    
    class TodoApp extends React.Component {
      handleAddTodo = () => {
        const todoText = this.input.value;
        this.props.addTodo(todoText);
        this.input.value = '';
      };
    
      render() {
        return (
          <div>
            <input type="text" ref={(input) => (this.input = input)} />
            <button onClick={this.handleAddTodo}>Add Todo</button>
            {this.props.todos.map((todo, index) => (
              <p key={index}>{todo}</p>
            ))}
          </div>
        );
      }
    }
    
    const mapStateToProps = (state) => ({
      todos: state.todos,
    });
    
    const mapDispatchToProps = {
      addTodo,
    };
    
    export default connect(mapStateToProps, mapDispatchToProps)(TodoApp);
    
    
  3. actions/actionCreators.js 文件中:

    export const addTodo = (text) => ({
          type: 'ADD_TODO',
          payload: text,
        });
    
  4. reducers/todoReducer.js 文件中:

    const initialState = {
    todos: [],
    };
    
    const todoReducer = (state = initialState, action) => {
      switch (action.type) {
        case 'ADD_TODO':
          return {
           ...state,
            todos: [...state.todos, action.payload],
          };
        default:
          return state;
      }
    };
    
    export default todoReducer;
    ```
    
  5. store/index.js 文件中:

    import { createStore } from 'redux';
    import todoReducer from './reducers/todoReducer';
    
    const store = createStore(todoReducer);
    
    export default store;
    
    

在这个示例中,通过 React Redux 实现了一个简单的待办事项应用。用户在输入框中输入待办事项,点击按钮后,通过派发 addTodo action 将待办事项添加到 Redux store 中,然后组件从 store 中获取 todos 状态并渲染出来。

十四、Redux中间件

Redux 中间件为 Redux 应用提供了强大的扩展能力,可以在 action 被派发和到达 reducer 之间进行各种处理。

一、原理

Redux 的核心是一个单一的 store,用于存储应用的状态,并且状态的更新是通过纯函数 reducer 来实现的。当一个 action 被派发时,Redux 会将这个 action 传递给所有的 reducer,reducer 根据 action 的类型来更新状态并返回新的状态。

中间件的原理是在 action 被派发后,到达 reducer 之前,拦截这个 action,并可以对 action 进行处理、异步操作、日志记录等各种操作。中间件通过函数组合的方式,将多个中间件链接在一起,形成一个处理链,每个中间件可以决定是否将 action 传递给下一个中间件或者直接传递给 reducer。

二、功能

  1. 异步操作处理:可以处理异步的 API 请求,使得在 Redux 应用中进行异步操作更加方便和易于管理。例如,可以使用中间件来发送异步请求,然后在请求成功或失败时派发相应的 action。
  2. 日志记录:记录 action 的派发和状态的变化,方便调试和分析应用的行为。
  3. 错误处理:捕获和处理在 action 处理过程中出现的错误。
  4. 性能优化:可以进行性能优化,如缓存数据、延迟执行某些操作等。

三、代码实例

以下是一个使用 Redux Thunk 中间件进行异步操作的示例:

  1. 安装 Redux Thunk:

    npm install redux-thunk
    
  2. 创建 Redux store,并应用中间件:

    import { createStore, applyMiddleware } from 'redux';
    import thunk from 'redux-thunk';
    import rootReducer from './reducers';
    const store = createStore(rootReducer, applyMiddleware(thunk));
    
    export default store;
    
    
  3. 定义 action creator:

export const fetchData = () => {
      return (dispatch) => {
        // 模拟异步请求
        setTimeout(() => {
          const data = { id: 1, name: 'Example Data' };
          dispatch({ type: 'FETCH_DATA_SUCCESS', payload: data });
        }, 1000);
      };
    };
  1. 定义 reducer:

    const initialState = {
    	data: null,
    };
    const dataReducer = (state = initialState, action) => {
      switch (action.type) {
        case 'FETCH_DATA_SUCCESS':
          return {
           ...state,
            data: action.payload,
          };
        default:
          return state;
      }
    };
    
    export default dataReducer;
    ```
    
  2. 在 React 组件中使用:

    import React from 'react';
    import { connect } from 'react-redux';
    import { fetchData } from './actions';
    
    class DataComponent extends React.Component {
      componentDidMount() {
        this.props.fetchData();
      }
    
      render() {
        const { data } = this.props;
        return (
          <div>
            {data? <p>{data.name}</p> : <p>Loading...</p>}
          </div>
        );
      }
    }
    
    const mapStateToProps = (state) => ({
      data: state.data,
    });
    
    export default connect(mapStateToProps, { fetchData })(DataComponent);
    ```
    

在这个例子中,Redux Thunk 中间件允许我们定义的fetchData action creator 返回一个函数而不是一个普通的 action 对象。这个函数接收dispatch作为参数,可以在其中进行异步操作,然后在异步操作完成后派发一个新的 action 来更新状态。

这样,通过中间件,我们可以在 Redux 应用中方便地进行异步操作,并且保持代码的清晰和可维护性。

十五、immutable.js

1、Immutable优点

  • Immutable是不可改变的,一旦创建就不再被更改的数据,对Immutable对象的任何修改删除添加操作都会返回一个新的Immutable对象
  • 实现的原理是Persistent Data Structure持久化数据结构
  • 在使用旧数据创建新数据时,尽可能利用之前的数据结构不对内存造成浪费,同时避免了deefcopy把所有节点copy造成的性能损耗

Immutable.js 中,虽然看起来使用 pushsetmerge 等方法像是在修改数据,但实际上这些方法并不会真正修改原始数据,而是返回一个新的不可变数据结构。

例如:

import { List } from 'immutable';

const originalList = List.of(1, 2, 3);
const newList = originalList.push(4);

console.log(originalList); // List [ 1, 2, 3 ]
console.log(newList); // List [ 1, 2, 3, 4 ]

在这里,调用 push 方法后,原始的 originalList 并没有被改变,而是生成了一个新的包含了新元素的 newList

这就是 Immutable.js 的核心特性之一,确保数据的不可变性,从而使得在复杂的应用中更容易管理状态和追踪变化,同时也能带来性能上的优化,因为可以通过简单的引用比较来判断数据是否发生了变化。

2、Redux中使用Immutable

Redux 可以与 Immutable.js 结合使用,以提高性能和确保状态的不可变性。Immutable.js 提供了不可变的数据结构,这意味着一旦创建,它们就不能被修改,而是返回一个新的不可变对象。

一、为什么结合使用 Redux 和 Immutable.js

  1. 性能优化:Immutable 对象在进行比较时非常高效,因为它们只需要比较对象的引用而不是深层遍历对象的每个属性。这可以显著提高 Redux 应用的性能,特别是在处理大型状态树时。
  2. 确保不可变性:使用 Immutable 对象可以确保状态的不可变性,这有助于避免由于意外的状态修改而导致的错误。在 Redux 中,状态应该是不可变的,以便可以准确地跟踪状态的变化和进行时间旅行调试。
  3. 更好的开发体验:Immutable.js 提供了一些方便的方法来操作不可变数据结构,例如使用mapfilterreduce等方法来转换和处理数据,这可以使代码更加简洁和易于理解。

二、代码实例

  1. 安装 Immutable.js:

    npm install immutable
    
  2. 创建 Redux store,并应用 Immutable.js:

    import { createStore, combineReducers } from 'redux';
    import { fromJS } from 'immutable';
    import todoReducer from './reducers/todoReducer';
    
    const initialState = fromJS({
      todos: [],
    });
    
    const rootReducer = combineReducers({
      todos: todoReducer,
    });
    
    const store = createStore(rootReducer, initialState);
    
    export default store;
    ```
    
  3. 定义 reducer:

    const initialState = fromJS([]);
    
    const todoReducer = (state = initialState, action) => {
      switch (action.type) {
        case 'ADD_TODO':
          return state.push(action.payload);
        case 'REMOVE_TODO':
          return state.filter((todo) => todo!== action.payload);
        default:
          return state;
      }
    };
    
    export default todoReducer;
    
  4. 定义 action creator:

    export const addTodo = (text) => ({
      type: 'ADD_TODO',
      payload: text,
    });
    
    export const removeTodo = (text) => ({
      type: 'REMOVE_TODO',
      payload: text,
    });
    
  5. 在 React 组件中使用:

    import React from 'react';
    import { connect } from 'react-redux';
    import { addTodo, removeTodo } from './actions';
    
    class TodoApp extends React.Component {
      handleAddTodo = () => {
        const todoText = this.input.value;
        this.props.addTodo(todoText);
        this.input.value = '';
      };
    
      handleRemoveTodo = (todo) => {
        this.props.removeTodo(todo);
      };
    
      render() {
        return (
          <div>
            <input type="text" ref={(input) => (this.input = input)} />
            <button onClick={this.handleAddTodo}>Add Todo</button>
            {this.props.todos.map((todo, index) => (
              <div key={index}>
                <p>{todo}</p>
                <button onClick={() => this.handleRemoveTodo(todo)}>Remove</button>
              </div>
            ))}
          </div>
        );
      }
    }
    
    const mapStateToProps = (state) => ({
      todos: state.get('todos'),
    });
    
    const mapDispatchToProps = {
      addTodo,
      removeTodo,
    };
    
    export default connect(mapStateToProps, mapDispatchToProps)(TodoApp);
    

在这个例子中,我们使用 Immutable.js 的fromJS方法将初始状态转换为 Immutable 对象。在 reducer 中,我们使用 Immutable 对象的方法来更新状态,例如pushfilter。在 React 组件中,我们使用mapStateToProps函数将 Immutable 对象转换为普通的 JavaScript 对象,以便在组件中使用。

通过结合使用 Redux 和 Immutable.js,我们可以提高应用的性能,确保状态的不可变性,并获得更好的开发体验。

十六、hook 是如何储存的?

1、Fiber链表结构

  • Fiber 节点有一个名为memoizedState的属性,用于存储与该组件相关联的状态。对于使用 Hook 的函数组件,memoizedState属性保存着由 Hook 创建的状态值和更新函数的引用。
  • Fiber 节点以链表结构连接,这使得 React 能够在渲染和协调过程中高效地遍历和更新组件树。这种链表结构使 React 能够跟踪 Hook 在不同渲染和组件中的调用顺序。

2、函数闭包

  • Hook 利用闭包来捕获并在多次渲染之间保留它们的状态。当一个函数在另一个函数内部被定义时,就会形成一个闭包,并且内部函数可以访问外部函数的变量。在 React Hook 的情况下,函数组件及其 Hook 形成一个闭包,允许状态在多次渲染之间持续存在。

十七、为什么不能在if语句中使用hook

在 React 的条件语句中不能直接放置 Hook 的调用是因为 React 依赖于 Hook 的调用顺序来正确地管理和追踪状态。

以下是详细解释:

一、React 对 Hook 调用顺序的严格要求

  1. 依赖固定调用顺序:React 需要确切地知道在一个组件的每次渲染中,Hook 是以相同的顺序被调用的。这是 React 内部实现状态管理和副作用追踪的关键机制。如果在条件语句中放置 Hook 的调用,那么不同的渲染可能会导致 Hook 的调用顺序不一致。

    • 例如,假设在一个组件中有这样的代码:
      function MyComponent() {
        const [count, setCount] = useState(0);
        if (someCondition) {
          const [anotherState, setAnotherState] = useState(false);
        }
        return <div>{count}</div>;
      }
      
    • 如果someCondition在不同的渲染中可能为真或假,那么useState(false)的调用就不是在每次渲染中都以相同的位置出现,这会破坏 React 对 Hook 调用顺序的预期。
  2. 确保状态与副作用的正确关联:React 通过固定的调用顺序来关联特定的状态和副作用与组件的渲染。如果 Hook 的调用顺序不固定,React 就无法准确地知道哪个状态属于哪个useState调用,哪个副作用属于哪个useEffect调用等。

    • 例如,在上面的例子中,如果anotherStateuseState调用在某些渲染中不存在,而在其他渲染中存在,那么 React 可能会错误地将其他状态或副作用与这个原本应该是anotherState的位置关联起来,导致不可预测的行为和错误。

二、违反规则的后果

如果在条件语句中使用 Hook,React 会在运行时抛出一个错误,提示你必须在 React 函数组件的顶层调用 Hook。这个错误是为了帮助开发者避免由于不按规则使用 Hook 而导致的难以调试的错误。

例如,如果你尝试运行上面包含在条件语句中使用 Hook 的代码,React 可能会抛出类似这样的错误:“Hooks can only be called inside the body of a function component.”(Hooks 只能在函数组件的主体中被调用。)

总之,为了确保 React 能够正确地管理状态和副作用,并且保证应用的可预测性和稳定性,必须在函数组件的顶层(不在条件语句、循环或嵌套函数中)调用 Hook。

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

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

相关文章

关于神经网络的一个介绍

这篇文章中&#xff0c;我将简单介绍下与神经网络有关的东西&#xff0c;包括它的基本模型&#xff0c;典型的算法以及与深度学习的联系等内容。 一、神经元 神经网络是由许多个神经元组成的&#xff0c;在生物的神经网络中&#xff0c;就是神经元间相互连接&#xff0c;传递…

Arthas getstatic(查看类的静态属性 )

文章目录 二、命令列表2.1 jvm相关命令### 2.1.7 getstatic&#xff08;查看类的静态属性 &#xff09; 二、命令列表 2.1 jvm相关命令 ### 2.1.7 getstatic&#xff08;查看类的静态属性 &#xff09; 使用场景&#xff1a; 我们项目部署在linux上&#xff0c;我有个本地内存…

从一到无穷大 #35 Velox Parquet Reader 能力边界

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。 本作品 (李兆龙 博文, 由 李兆龙 创作)&#xff0c;由 李兆龙 确认&#xff0c;转载请注明版权。 文章目录 引言源码分析功能描述功能展望 引言 InfluxDB IOX这样完全不使用索引&#xff0c;只…

《沧浪之水》读后感

未完待续..... 未完待续.... 未完待续.... 【经典语录】 01、我一辈子的经验就是不要做瞎子&#xff0c;也不能做聋子&#xff0c;该听到的信息要听到&#xff0c;但是要做哑巴&#xff0c;看到了听到了心中有数就行了&#xff0c;可千万不要张口说什么。 02、你刚从学校毕业…

MQ入门(一):同步调用和异步调用--RabbitMQ基础入门

目录 1.初识MQ 1.1.同步调用 1.2.异步调用 1.3.技术选型 2.RabbitMQ 2.1.安装部署 2.2.RabbitMQ基本架构 2.3.收发消息 2.3.1.交换机 2.3.2.队列 2.3.3.绑定关系 2.3.4.发送消息 2.4.数据隔离 2.4.1.用户管理 2.4.2.virtual host 1.初识MQ 微服务一旦拆分&…

web前端字段大小写下划线转换工具

文章目录 前言一、如何使用&#xff1f;二、相关代码总结 前言 程序员在敲代码的过程中都要命名一些字段&#xff0c;但是Java语言对字段的命名规范和sql命名规范不一样&#xff0c;如下图所示&#xff0c;这种机械性的转换工作很劳神费力&#xff0c;为了省点劲写了一个web小…

尚品汇-Jenkins部署构建服务模块、Linux快照备份(五十七)

目录&#xff1a; &#xff08;1&#xff09;构建作业&#xff08;server-gateway&#xff09; &#xff08;2&#xff09;构建service_product模块 &#xff08;3&#xff09;演示添加新代码 &#xff08;4&#xff09;学会使用linux快照 &#xff08;1&#xff09;构建作…

在SpringCloud中实现服务间链路追踪

在微服务架构中&#xff0c;由于系统的复杂性和多样性&#xff0c;往往会涉及到多个服务之间的调用。当一个请求经过多个服务时&#xff0c;如果出现问题&#xff0c;我们希望能够快速定位问题所在。这就需要引入链路追踪机制&#xff0c;帮助我们定位问题。 Spring Cloud为我们…

【沪圈游戏公司作品井喷,游戏产业复兴近在眼前】

近期财报季中&#xff0c;腾讯、网易及B站等国内游戏巨头纷纷亮出亮眼的游戏业务表现&#xff0c;均实现了接近或超越双位数的同比增长。然而&#xff0c;审视过去一年&#xff0c;国内游戏行业仍笼罩在宏观经济“降本增效”的阴影下。 行业数据揭示&#xff0c;全国游戏公司社…

封装 wx.request 的必要性及其实现方式

目录 为什么需要封装 wx.request 1. 避免回调地狱 2. 统一管理 3. 扩展功能 小程序异步 API 的改进 封装实现方式 在小程序开发中&#xff0c;网络请求是不可或缺的功能之一。小程序提供了 wx.request API 来实现网络请求&#xff0c;但直接使用这个 API 在复杂场景下可…

关于SpringBoot项目使用maven打包由于Test引起的无法正常打包问题解决

一、问题描述 在日常工作中&#xff0c;在接手项目时&#xff0c;项目未必是“正常”的&#xff0c;一般平常搭建项目&#xff0c;都不会采用一键式生成的方式&#xff0c;现在说下旧项目&#xff0c;可能项目结构并不是那么简洁&#xff0c;通常都带有与main同层级的test&…

Cpp类和对象(中续)(5)

文章目录 前言一、赋值运算符重载运算符重载赋值运算符重载赋值运算符不可重载为全局函数前置和后置的重载 二、const修饰成员函数三、取地址及const取地址操作符重载四、日期类的实现构造函数日期 天数日期 天数日期 - 天数日期 - 天数日期类的大小比较日期类 > 日期类日…

嵌入式系统stm32cube本地安装出现的问题

stm32cube在线安装很慢&#xff0c;本地安装中出现的一个bug stm32cube_fw_f4_v1281安装成功之后&#xff0c;如果想安装stm32cube_fw_f4_v1281会提示stm32cube_fw_f4_v1280未安装。 如果先安装stm32cube_fw_f4_v1280之后&#xff0c;再安装stm32cube_fw_f4_v1281还会提示这个…

Python模拟鼠标轨迹[Python]

一.鼠标轨迹模拟简介 传统的鼠标轨迹模拟依赖于简单的数学模型&#xff0c;如直线或曲线路径。然而&#xff0c;这种方法难以捕捉到人类操作的复杂性和多样性。AI大模型的出现&#xff0c;能够通过深度学习技术&#xff0c;学习并模拟更自然的鼠标移动行为。 二.鼠标轨迹算法实…

C#如何把写好的类编译成dll文件

1 新建一个类库项目 2 直接改写这个Class1.cs文件 3 记得要添加Windows.Forms引用 4 我直接把在别的项目中做好的cs文件搞到这里来&#xff0c;连文件名也改了&#xff08;FilesDirectory.cs&#xff09;&#xff0c;这里using System.Windows.Forms不会报错&#xff0c;因为前…

go项目多环境配置

1.java项目配置加载最佳实践 在 Spring Boot 项目中&#xff0c;配置文件的加载和管理是开发过程中不可或缺的一部分。Spring Boot 提供了一套灵活且强大的机制来加载配置文件&#xff0c;使得开发者能够根据不同的环境和需求轻松地管理配置。当多个位置存在相同的配置文件时&…

Python语法进阶之路

一、Python基础 1.1 注释 定义和作用 对代码解释说明&#xff0c;增强可读性 单行注释 # 多行注释 """ 这是一个多行注释 """ 1.2 变量及变量类型 定义和作用 计算机目的是计算&#xff0c;编程是为了更方便计算&#xff0c;计算对象就是…

vue循环渲染动态展示内容案例(“更多”按钮功能)

当我们在网页浏览时&#xff0c;常常会有以下情况&#xff1a;要展示的内容太多&#xff0c;但展示空间有限&#xff0c;比如我们要在页面的一部分空间中展示较多的内容放不下&#xff0c;通常会有两种解决方式&#xff1a;分页&#xff0c;“更多”按钮。 今天我们的案例用于…

mybatis 配置文件完成增删改查(二):根据条件查询一个

文章目录 参数占位符#{}:会将其替换为&#xff1f; ——为了防止sql注入${}:会将其替换为实际接收到的数据&#xff0c;拼sql ——无法防止sql注入 查询一个sql特殊字符的处理 参数占位符 #{}:会将其替换为&#xff1f; ——为了防止sql注入 ${}:会将其替换为实际接收到的数据…

Java继承教程!(o|o)

Java 继承 Java面向对象设计 - Java继承 子类可以从超类继承。超类也称为基类或父类。子类也称为派生类或子类。 从另一个类继承一个类非常简单。我们在子类的类声明中使用关键字extends&#xff0c;后跟超类名称。 Java不支持多重继承的实现。 Java中的类不能有多个超类。…