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

一、类组件与函数式组件

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.xdnf.cn/news/147016.html

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

相关文章

故障诊断│GWO-DBN灰狼算法优化深度置信网络故障诊断

1.引言 随着人工智能技术的快速发展&#xff0c;深度学习已经成为解决复杂问题的热门方法之一。深度置信网络&#xff08;DBN&#xff09;作为深度学习中应用比较广泛的一种算法&#xff0c;被广泛应用于分类和回归预测等问题中。然而&#xff0c;DBN的训练过程通常需要大量的…

机器人速度雅可比矩阵(机器人动力学)

博途PLC矩阵求逆 矩阵求逆 博图SCL_博图矩阵运算-CSDN博客文章浏览阅读839次。本文介绍如何用C语言实现矩阵求逆的过程,详细解析了相关代码,适合线性代数和编程爱好者学习。https://rxxw-control.blog.csdn.net/article/details/122367883 1、二自由度平面关节机器人速度雅…

项目第十二弹:功能联调

项目第十二弹&#xff1a;功能联调 一、发布订阅功能测试1.生产者2.消费者3.演示4.持久化信息查看1.消息2.SQLite3数据库 二、持久化恢复测试1.代码2.gc3.演示 三、虚拟机和信道隔离测试1.责任划分2.如何测试3.生产者4.消费者5.演示 一、发布订阅功能测试 我们直接上TOPIC交换…

MySQL中的逻辑条件

逻辑条件组合两个比较条件的结果来产生一个基于这些条件的单个的结果&#xff0c;或者逆转一个单个条件的结果。当所有条件的结果为真时&#xff0c;返回行。 SQL的三个逻辑运算符是&#xff1a; AND、OR、NOT 可以在WHERE子句中用AND和OR运算符使用多个条件。 示例一&#…

惊爆!高通要收购英特尔,巨头也会被时代抛弃!

今天看到的外媒消息&#xff0c;高通要收购英特尔&#xff0c;看到消息的时候&#xff0c;其实&#xff0c;还是挺吃惊的。 高通是移动芯片的王者&#xff0c;英特尔是 PC 芯片的王者。当然了&#xff0c;英特尔这个可能需要再加上两个字&#xff1a;曾经的 PC 芯片王者。 其实…

植物大战僵尸【源代码分享+核心思路讲解】

植物大战僵尸已经正式完结&#xff0c;今天和大家分享一下&#xff0c;话不多说&#xff0c;直接上链接&#xff01;&#xff01;&#xff01;&#xff08;如果大家在运行这个游戏遇到了问题或者bug&#xff0c;那么请私我谢谢&#xff09; 大家写的时候可以参考一下我的代码思…

在VMware16中安装Windows 10:完整教程

在VMware中安装Windows 10&#xff1a;完整教程 1.安装环境准备2.创建虚拟机 1.安装环境准备 1.虚拟机: VMware-workstation-full-16.2.2-19200509 2.系统镜像:win10 2.创建虚拟机 1.自定义 2.下一步 3.稍后安装系统 3.默认下一步 4.虚拟机取名和选择存放路径(按需更改…

利士策分享,江西新余悲剧背后的深思:安全与责任的重构

利士策分享&#xff0c;江西新余悲剧背后的深思&#xff1a;安全与责任的重构 在这个信息瞬息万变的时代&#xff0c;每一次突发事件都能迅速触动社会的神经&#xff0c; 而江西新余近期发生的悲剧&#xff0c;更是让我们在悲痛之余&#xff0c;不得不深刻反思安全管理与社会…

AVL树与红黑树

目录 AVL树 AVL树节点的定义 AVL树的插入 AVL树的旋转 右单旋 左单旋 左右双旋 右左双旋 AVL树的验证 AVL树的性能 红黑树 红黑树的性质 红黑树节点的定义 红黑树结构 红黑树的插入操作 按照二叉搜索的树规则插入新节点 检测新节点插入后&#xff0c;红黑树的性…

升级你的HarmonyOS体验:一窥功能引导与拖拽交换的独家技巧

文章目录 前言项目目录结构开发流程主要步骤讲解关键配置Index.ets 页面讲解高光组件相关HeaderApp 总结 前言 在当今的移动应用开发领域&#xff0c;为了提供更加友好和直观的用户体验&#xff0c;开发者们通常会集成多种交互功能来增强应用的互动性和易用性。在这些功能中&a…

【机器学习】12-决策树1——概念、特征选择

机器学习10-决策树1 学习样本的特征&#xff0c;将样本划分到不同的类别&#xff08;分类问题&#xff09;或预测连续的数值&#xff08;回归问题&#xff09;。 选择特征&#xff0c;划分数据集&#xff0c;划分完成形成模型&#xff08;树结构&#xff09;&#xff0c;一个…

JavaSE——多线程基础

概述 现代操作系统&#xff08;Windows&#xff0c;macOS&#xff0c;Linux&#xff09;都可以执行多任务。多任务就是同时允许多个任务。例如&#xff1a;播放音乐的同时&#xff0c;浏览器可以进行文件下载&#xff0c;同时可以进行QQ消息的收发。 CPU执行代码都是一条一条顺…

Matlab R2018a怎么下载安装?Matlab R2018a保姆级详细安装教程

Matlab R2018a下载方法&#xff1a; Matlab R2018a安装教程&#xff1a; 1、右击下载好的压缩包&#xff0c;选择解压到Matlab R2018a 2、打开文件夹【R2018a_win64】&#xff0c;右击下面的setup.exe&#xff0c;选择【以管理员身份运行】 3、点击选择【使用文件安装密钥】&a…

IDEA连接数据库报错:Access denied for user ****

使用IDEA开发时&#xff0c;通过Databse连接数据库。多次连接报错&#xff1a;Access denied for user **** 如下所示&#xff1a; ​ ‍ ‍ ​ ‍ 花了不少时间排查&#xff0c;确认账号、密码&#xff0c;后面发现账号后多了个空格&#xff0c;而且不容易发现&#xf…

proteus仿真软件简体中文版网盘资源下载(附教程)

对于电子通信专业的小伙伴来说&#xff0c;今天文章的标题应该不会陌生。Proteus是一款具有广泛应用的仿真软件&#xff0c;它的功能非常强大&#xff0c;适用于所有单片机的仿真工作&#xff0c;能够从原理图、调试、到与电路的协同仿真一条龙全部搞定&#xff0c;受到所有用户…

交叉熵损失函数的使用

交叉熵损失函数 交叉熵损失函数&#xff08;Cross-Entropy Loss&#xff09;&#xff0c;也称为对数损失&#xff08;Log Loss&#xff09;&#xff0c;是机器学习和深度学习中常用的损失函数之一&#xff0c;尤其在分类问题中。它衡量的是模型预测的概率分布与真实标签的概率…

使用Properties

a.特点 i.它的Key-Value一般都是String-String类型的&#xff0c;可以用Map<String, String>表示。 ii.Java标准库提供Properties来表示一组“配置”。 iii.读写Properties时&#xff0c;使用getProperty()和setProperty()方法&#xff0c;不要调用继承自HashTabled的ge…

开始场景的制作+气泡特效的添加

3D场景或2D场景的切换 1.新建项目时选择3D项目或2D项目 2.如下图操作&#xff1a; 开始前的固有流程 按照如下步骤进行操作&#xff0c;于步骤3中更改Company Name等属性&#xff1a; 本案例分辨率可以如下设置&#xff0c;有能力者可根据需要自行调整&#xff1a; 场景制作…

轻掺杂漏极(LDD)技术

轻掺杂漏极&#xff08;LDD&#xff09;是一种低能量、低电流的注入工艺&#xff0c;通过该工艺在栅极附近形成浅结&#xff0c;以减少靠近漏极处的垂直电场。对于亚微米MOSFET来说&#xff0c;LDD是必需的&#xff0c;以便抑制热电子效应&#xff0c;这种效应会导致器件退化并…

Python进阶学习笔记(一)对象

1.对象模型 在面向对象理论中类和对象是不同的概念&#xff0c;而在python中类也是对象&#xff0c;叫做类型对象。 所以python中的类&#xff0c;实例对象&#xff0c;类型都是对象。 元类型&#xff1a; 在python中实例对象的类型为对应类型的对象&#xff0c;而类型的对象…