react的理解

1、概念

React 是一个用于构建用户界面的 JavaScript 库,由 Facebook(现 Meta)开发并开源。它专注于视图层,采用声明式编程范式,让开发者能够高效、灵活地创建交互式 UI;

特点

1、组件化

React 应用由独立的、可复用的组件构成。每个组件封装了自己的结构(HTML 模版)、样式和逻辑,通过组合这些组件可以搭建出复杂的界面。组件可以是函数组件或类组件。

2、虚拟 DOM

React 在内存中维护一棵轻量级的虚拟 DOM 树。当组件的状态或属性发生变化时,React 会先计算出虚拟 DOM 的变化(diffing 算法),然后批量更新真实 DOM,从而减少昂贵的 DOM 操作,提升性能。

3、声明式编程

开发者只需描述界面应该呈现的状态(如"如果数据是加载中,显示 loading;否则显示列表"),React 会自动管理和更新 UI 来匹配这个状态,使代码更易理解和维护。

4、单向数据流

数据从父组件通过 props 向下传递到子组件,子组件不能直接修改父组件的数据,只能通过回调函数通知父组件。这种单向流动让数据变化更可预测。

5、JSX语法

一种 JavaScript 语法扩展,允许在 JavaScript 代码中编写类似 HTML 的标记,最终被编译为普通的 JavaScript 对象。JSX 让组件的结构更直观。

2、组件

1、组件的两种主要形式

函数组件:最简单的定义方式,它是一个接收 props 对象并返回 JSX 的 JavaScript 函数

js 复制代码
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

类组件:使用 ES6 class 定义,必须继承 React.Component,并实现 render() 方法。类组件可以拥有内部状态(state)和生命周期方法

js 复制代码
class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

2、核心概念:Props 与 State

Props(属性):父组件传递给子组件的只读数据,子组件不能修改自己的 props

State(状态):组件内部管理的数据,可以通过 setState(类组件)或 useState Hook(函数组件)来更新,状态的改变会触发组件重新渲染

js 复制代码
class Welcome extends React.Component {
	state = {
		n:123
	}
	function add(){
		this.setState({ n: 20 })
	}
  render() {
    return <h1>Hello, {this.state.n}</h1>;
  }
}

3、事件

react中dom事件采用小驼峰命名,js中dom事件是小写,react中自定义事件通过props传递接收并执行

函数组件

js 复制代码
function Button() {
  const handleClick = () => {
    console.log('按钮被点击');
  };
  return <button onClick={handleClick}>点击我</button>;
}

类组件(注意 this 绑定)

如果没有特殊处理,在事件处理函数中,this指向undefined

两种处理方式:1、通过bind指向this;2、声明箭头函数

js 复制代码
class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isOn: true };
    // 为了在回调中使用 `this`,需要绑定
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
  		this.props.fn(123);
    	this.setState(prevState => ({ isOn: !prevState.isOn }));
  }
  // handleClick = () => {}

  render() {
    return (
      <button onClick={this.handleClick} onMouseEnter={()=>{}}>
        {this.state.isOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}

4、组件通信

父传子:父组件传递数据给子组件,子组件通过props接收

js 复制代码
// 子组件:接收 props
function Child({ name, age }) {
  return (
    <div>
      <p>姓名:{name}</p>
      <p>年龄:{age}</p>
    </div>
  );
}

// 父组件:传递数据
function Parent() {
  const user = { name: '张三', age: 20 };
  return <Child name={user.name} age={user.age} />;
}

子传父:子组件调用props接收的函数,把参数传回去

js 复制代码
// 子组件:接收父组件传来的回调函数
function Child({ onMessage }) {
  const handleClick = () => {
    const data = '子组件的数据';
    onMessage(data); // 调用父组件传入的函数
  };

  return <button onClick={handleClick}>发送消息给父组件</button>;
}

// 父组件:定义回调函数并传给子组件
function Parent() {
  const handleChildMessage = (msg) => {
    console.log('来自子组件的消息:', msg);
  };

  return <Child onMessage={handleChildMessage} />;
}

3、理解setState

1、基本用法:setState({}, callback)

2、异步性:如果每次 setState 都立即触发渲染,那么多次调用会造成不必要的性能开销;为避免频繁的重复渲染,调用 setState 后,状态并不会立即改变,而是进入一个更新队列,React 会在事件处理函数执行完后批量处理这些更新,然后合并状态并重新渲染(注意:只有当setState处于html事件中时才是异步的)

3、合并更新:对象形式的 setState 会被合并,相同的属性会被后赋值的覆盖,函数形式的 setState 可以基于前一个状态计算新状态

js 复制代码
class Index extends React.Component {
  state = {
    count: 0,
  };
  handleClick = () => {
    this.setState(
      {
        count: this.state.count + 1,
      },
      () => {
        console.log(this.state.count);
      }
    );
  };
  render() {
    return (
      <div>
        <h1>hello world</h1>
      </div>
    );
  }
}

4、生命周期(16.3 新版本)

挂载阶段

constructor:初始化 state 和绑定事件处理函数

static getDerivedStateFromProps(props, state):静态方法,在渲染前调用,根据 props 更新 state

render:解析 JSX,生成虚拟 DOM

componentDidMount:组件挂载后立即调用,适合处理数据请求、添加订阅等副作用操作

更新阶段

static getDerivedStateFromProps(props, state):静态方法,在组件因 props 更新、setState 或 forceUpdate 而重新渲染时都会被调用

shouldComponentUpdate(nextProps, nextState):决定组件是否重新渲染,常用于性能优化

render:重新解析 JSX 以生成新的虚拟 DOM

getSnapshotBeforeUpdate(prevProps, prevState):在最近一次渲染输出(提交到 DOM 之前)之前调用。它让组件能在 DOM 更新前捕获一些信息(如滚动位置),返回值将作为参数传递给 componentDidUpdate

componentDidUpdate(prevProps, prevState, snapshot):组件更新完成后调用,可以操作更新后的 DOM,或根据 snapshot 进行后续处理

卸载阶段
componentWillUnmount:组件卸载前调用,用于执行清理操作,如取消网络请求、移除定时器等

新版本移除了componentWillMount、componentWillReceiveProps、componentWillUpdate等生命周期函数

5、高阶组件

1、高阶函数:接收一个函数作为参数,且返回一个函数

2、高阶组件:接收一个组件,返回一个新组件

3、高阶组件的优势:

逻辑复用:当多个组件需要共享相同的逻辑(如权限检查、日志记录、数据获取、样式注入)时,可以将这些逻辑提取到 HOC 中

横切关注点:将关注点(如日志、认证)与组件本身分离,使组件更纯粹、更专注于 UI 渲染

增强组件:在不修改原始组件代码的情况下,为组件添加额外功能

js 复制代码
// 定义高阶组件
function fn<P extends object>(WrappedComponent: ComponentType<P>) {
  return class fnComponent extends Component<P & { loading?: boolean }> {
    render() {
      const { loading, ...props } = this.props;
      if (loading) {
        return <div>加载中...</div>;
      }
      return <WrappedComponent {...(props as P)} />;
    }
  };
}

// 使用高阶组件
const UserCard: React.FC<UserProps> = ({ name }) => <div>用户:{name}</div>;

// 传入一个组件,返回一个新组件,该组件为高阶组件
const UserCardWithLoading = withLoading(UserCard);  

const App = () => (
  <div>
    <UserCardWithLoading name="张三" loading={true} />
    <UserCardWithLoading name="李四" loading={false} />
  </div>
);

6、Ref

在 React 中,ref提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素;它让我们能够直接操作子元素或组件实例,适用于需要命令式操作(如焦点管理、媒体播放、与第三方 DOM 库集成)的场景

1、类组件

js 复制代码
class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();  // 创建 ref
    this.comRef = React.createRef();
  }

  componentDidMount() {
    this.myRef.current.focus();      // 访问 DOM 节点
    this.comRef.current.focus();      // 访问 组件实例
  }

  render() {
    return <div>
    <input ref={this.myRef} />
    <Component ref={this.comRef} />
    </div>;
  }
}

2、函数组件

js 复制代码
import { useRef, useEffect } from 'react';

function MyComponent() {
  const inputRef = useRef(null);

  useEffect(() => {
    inputRef.current.focus();
  }, []);

  return <input ref={inputRef} />;
}

3、forwardRef

在 React 中,ref 的作用是获取对 DOM 节点或类组件实例的引用;但函数组件默认没有实例,因此不能像类组件那样直接接收 ref 属性(即给函数组件添加 ref 会收到警告,且 ref 不会自动传递给组件内部的任何元素);forwardRef 正是为了允许函数组件接收 ref,并将其转发给子组件或 DOM 元素

1、使用 forwardRef 包装函数组件

js 复制代码
import React, { useRef, useEffect } from 'react';

// 使用 forwardRef 包装函数组件
const FancyInput = React.forwardRef((props, ref) => {
  return <input ref={ref} className="fancy-input" {...props} />;
});

function Parent() {
  const inputRef = useRef();

  useEffect(() => {
    // 父组件可以直接操作子组件内部的 input 元素
    inputRef.current.focus();
  }, []);

  return <FancyInput ref={inputRef} placeholder="请输入" />;
}

2、阶组件中的 ref 转发

当使用高阶组件(HOC)包装组件时,原有的 ref 会指向 HOC 外层容器,而不是被包装的组件。forwardRef 可以用来将 ref 传递给被包装的组件

js 复制代码
// 一个简单的日志 HOC
function withLog(WrappedComponent) {
  class WithLog extends React.Component {
    componentDidMount() {
      console.log('组件已挂载');
    }
    render() {
      // 注意:这里 ref 是特殊属性,不会作为 props 传递
      return <WrappedComponent {...this.props} />;
    }
  }
  return WithLog;
}

// 使用 forwardRef 解决 ref 传递问题
function withLog(WrappedComponent) {
  class WithLog extends React.Component {
    componentDidMount() {
      console.log('组件已挂载');
    }
    render() {
      const { forwardedRef, ...rest } = this.props;
      return <WrappedComponent ref={forwardedRef} {...rest} />;
    }
  }
  return React.forwardRef((props, ref) => {
    return <WithLog {...props} forwardedRef={ref} />;
  });
}

3、结合 useImperativeHandle 自定义暴露给父组件的实例值

js 复制代码
import React, { useRef, useImperativeHandle, forwardRef } from 'react';

const FancyInput = forwardRef((props, ref) => {
  const inputRef = useRef();

  // 自定义暴露给父组件的实例值
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    },
    clear: () => {
      inputRef.current.value = '';
    }
  }));

  return <input ref={inputRef} {...props} />;
});

function Parent() {
  const fancyRef = useRef();

  const handleFocus = () => {
    fancyRef.current.focus();
  };
  const handleClear = () => {
    fancyRef.current.clear();
  };

  return (
    <>
      <FancyInput ref={fancyRef} />
      <button onClick={handleFocus}>聚焦</button>
      <button onClick={handleClear}>清空</button>
    </>
  );
}

7、context

React Context 提供了一种在组件树中共享数据的方式,而无需通过 props 逐层手动传递。它主要用于解决 prop drilling(属性钻取)的问题,即当多个深层次组件需要访问同一份数据时,可以使用 Context 跨层级直接传递

1、核心概念

Context 对象:通过 React.createContext 创建,包含 Provider 和 Consumer 两个组件

Provider:数据的提供者,接收一个 value 属性,将数据传递给所有子孙组件

Consumer:数据的消费者(函数组件中可以使用 useContext Hook 替代)

2、基本使用

js 复制代码
import React, { useState, useContext, createContext } from 'react';

// 1. 创建 Context
const ThemeContext = createContext();

// 2. 创建 Provider 组件(封装了状态逻辑)
function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  const toggleTheme = () => {
    setTheme(prev => (prev === 'light' ? 'dark' : 'light'));
  };

  // 传递给子组件的值
  const value = {
    theme,
    toggleTheme
  };

  return (
    <ThemeContext.Provider value={value}>
      {children}
    </ThemeContext.Provider>
  );
}

// 3. 消费 Context 的组件
function ThemedButton() {
  const { theme, toggleTheme } = useContext(ThemeContext);
  return (
    <button
      onClick={toggleTheme}
      style={{
        background: theme === 'light' ? '#fff' : '#333',
        color: theme === 'light' ? '#000' : '#fff'
      }}
    >
      当前主题:{theme},点击切换
    </button>
  );
}

// 4. 顶层组件
function App() {
  return (
    <ThemeProvider>
      <div>
        <h1>Context 主题示例</h1>
        <ThemedButton />
      </div>
    </ThemeProvider>
  );
}

export default App;

8、PureComponent 纯组件(类组件)、React.memo

1、PureComponent (性能优化)

React.PureComponent 与 React.Component 类似,区别在于它已经内置了 shouldComponentUpdate 的实现,会对新旧 props 和 state 进行浅比较,只有当数据发生变化时才会触发重新渲染

注意:PureComponent 只进行一层比较(shallow compare),对于嵌套对象或数组,修改内部属性时引用并未改变,因此组件不会更新,如果需要对比数组,建议使用不可变数据,每次更新都返回新的对象/数组

应用:点击"添加"按钮时,只有新增的 ListItem 会渲染,已有的项因为 props.item 引用未变(利用展开运算符创建了新数组,但原有项对象引用未变),所以不会重新渲染

js 复制代码
class ListItem extends React.PureComponent {
  render() {
    const { item } = this.props;
    console.log('ListItem 渲染:', item.id);
    return <div>{item.name}</div>;
  }
}

class List extends React.Component {
  state = {
    items: [
      { id: 1, name: 'Apple' },
      { id: 2, name: 'Banana' }
    ]
  };

  addItem = () => {
    const newItem = { id: Date.now(), name: 'New' };
    this.setState({ items: [...this.state.items, newItem] });
  };

  render() {
    return (
      <div>
        <button onClick={this.addItem}>添加</button>
        {this.state.items.map(item => (
          <ListItem key={item.id} item={item} />
        ))}
      </div>
    );
  }
}

2、React.memo(性能优化)

React.memo 是一个高阶函数组件,它会比较当前 props 与下一个 props 的属性值,当新的 props 与旧的 props 属性值不相等时,才会重新渲染组件,默认是进行浅比较

js 复制代码
    const ChildComponent = React.memo((props) => {
    // 子组件内容
    return (
        <div>
        {props.value}
        </div>
    );
    });

9、Portal

React Portal 是 React 提供的一种将子节点渲染到父组件 DOM 层次结构之外的 DOM 节点中的能力。它允许你将组件的内容"传送"到 DOM 树的任意位置,同时保留 React 组件树的上下文(如 props、事件冒泡等)

1、应用场景

模态框(Modal):需要显示在所有页面内容之上,通常放在 的直接子级

工具提示(Tooltip):需要相对于某个元素定位,但不应被父元素的 overflow: hidden 或 z-index 限制

悬浮卡片(Dropdown)、通知提醒等

2、基本使用

js 复制代码
import React from 'react';
import ReactDOM from 'react-dom';

function PortalExample() {
  return ReactDOM.createPortal(
    <div className="portal-content">
      这段内容被渲染到 body 下
    </div>,
    document.body // 目标 DOM 节点
  );
}

10、hooks

useState

useState 用来存储变量,useState 接收一个初始值,返回一个数组,数组的第一个元素是变量的值,第二个元素是更新变量的函数,当改变变量的值时,组件会重新渲染

js 复制代码
    const [ count , setCount ] = useState(0)
    <button onClick={()=>{setCount(count+1)}}>{count}</button>

useReactive

useReactive 的使用和 useState 的使用一样,只是 useReactive 返回的是一个响应式的对象,而不是一个变量,更新对象的时候,组件会重新渲染

js 复制代码
    const state = useReactive({
        count: 0
    })
    <button onClick={()=>{state.count++}}>{state.count}</button>

useSetState

useSetState 的使用和 useState 的使用一样,只是 useSetState 返回的是一个响应式的对象,而不是一个变量,更新对象的时候,组件会重新渲染

js 复制代码
    const [state, setState] = useSetState({
        count: 0
    })
    <button onClick={()=>{setState({count: state.count + 1})}}>{state.count}</button>

useEffect

useEffect 是 React 中的一个钩子函数,用于处理副作用操作。副作用是指在组件渲染过程中,可能会对外部环境产生影响的操作,比如数据获取、订阅事件、操作 DOM 等

js 复制代码
    // dom初次渲染完成时执行,只执行一次,在此可以进行数据请求、订阅事件、操作 DOM 等操作
    useEffect(() => {
    },[])

     // 当依赖值发生变化时执行(当依赖值为对象或数组时,只有当引用地址发生变化时才会执行,如果只是修改了对象或数组中的某个属性时,不会执行)
    const [count, setCount] = useState(0);
    useEffect(() => {
    }, [count]);

    // 清除副作用操作:副作用函数可以返回一个清除函数,用于清除副作用操作,比如取消订阅、清除定时器等
    useEffect(() => {
    const timer = setInterval(() => {
    }, 1000);
    return () => {
        clearInterval(timer);
    };
    }, []);

useLayoutEffect(不常用)

useLayoutEffect 是 React 提供的一个 Hook,其函数签名与 useEffect 完全相同,但执行时机不同。它会在真实DOM 变更之后,且浏览器进行绘制之前执行。这使得它适合在浏览器绘制前读取 DOM 布局并同步触发重新渲染;

应用场景:

1、测量 DOM 节点:获取元素尺寸、位置等,并基于这些值同步更新 UI(如 tooltip 定位、动态调整样式)

2、避免不必要的闪烁:当需要在渲染后立即修改 DOM,且不希望用户看到中间状态时

3、与第三方库集成:有些库需要直接操作 DOM 并依赖布局信息,可以在 useLayoutEffect 中进行初始化

4、滚动条位置恢复:在更新内容后需要立即恢复滚动位置

注意:由于 useLayoutEffect 是同步执行的,如果回调中执行了耗时操作,会延迟浏览器绘制,导致页面卡顿(会阻塞浏览器绘制)

js 复制代码
useLayoutEffect(() => {
  // 副作用逻辑
  return () => {
    // 清理逻辑(可选)
  };
}, [dependencies]);

useMemo(性能优化)

useMemo 会在依赖项变化时重新计算新值,返回一个值(任何数据类型)

js 复制代码
    const ChildComponent =({ value })=> {
    // 使用useMemo来避免在value变化时重复渲染
    const expensiveOperation = useMemo(() => {
        // 进行一些昂贵的操作
        return performExpensiveOperation(value);
    }, [value]);

    return (
        <div>
        {expensiveOperation}
        </div>
    );
}

useCallback(性能优化)

useCallback 的用法与 useState 的用法基本一致,但最后会返回一个函数,用一个变量保存起来。返回的函数 a 会根据 b 的变化而变化,如果 b 始终未发生变化,a 也不会重新生成,避免函数在不必要的情况下更新

为什么需要该hook?

在 React 函数组件中,每次渲染都会重新创建内部定义的函数。如果将这些函数作为 props 传递给子组件,而子组件使用了 React.memo 进行优化,那么由于每次父组件渲染时函数引用都变了,子组件会认为 props 发生了变化,导致即使其他 props 没变,子组件也会重新渲染。useCallback 通过稳定函数引用,配合 React.memo 来减少这种不必要的渲染

js 复制代码
    const fn = useCallback(() => {
			return function() {
				console.log(b)
			}
    },[b])

useRef

useRef 是 React 提供的一个 Hook,它返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数;这个 ref 对象在组件的整个生命周期内保持不变(即每次渲染返回同一个对象),但可以修改它的 .current 属性来存储任意值

应用场景:

1、访问 DOM 元素

js 复制代码
    const scrollRef = useRef(null);
    console.log(scrollRef.current);
     <div ref={scrollRef} />

2、 存储可变值(跨渲染持久化)-> 当你需要一个值,在组件重新渲染后依然保持不变,并且修改它时不想触发重新渲染,就可以用 useRef

js 复制代码
function Timer() {
  const timerIdRef = useRef();

  const startTimer = () => {
    timerIdRef.current = setInterval(() => {
      console.log('tick');
    }, 1000);
  };

  const stopTimer = () => {
    clearInterval(timerIdRef.current);
  };

  return (
    <>
      <button onClick={startTimer}>开始</button>
      <button onClick={stopTimer}>停止</button>
    </>
  );
}

useCreation(性能优化)

来自于ahooks库,它是一个旨在强化和替代 useMemo 与 useRef 的自定义 Hook,主要用于精细化的性能优化

为什么需要 useCreation?

1、更可靠的 useMemo:React 官方文档曾提及,useMemo 不保证缓存的值永远不会被回收。在内存紧张时,React 可能会销毁之前缓存的值以便释放内存,下次渲染时即使依赖未变也会重新计算 。useCreation 则通过 useRef 来持有对象,保证了缓存值的稳定性,只要依赖不变,它绝不会被垃圾回收机制意外清除 。

2、避免 useRef 的重复创建开销:当使用 useRef 来存储一个创建开销很大的对象时,你可能会这样写:const subject = useRef(new Subject())。但问题在于,每次组件渲染,new Subject() 这个昂贵的构造函数都会被执行,尽管最终只有第一次的值被用到 。useCreation 接收一个工厂函数,它只在首次渲染和依赖变化时才执行这个函数创建对象,完美避免了重复的性能损耗

3、useCreation 的核心思想是结合 useRef 的持久化能力和手动依赖检查

js 复制代码
    const a = useCreation(() => {
        return b + 1
    },[b])

useMount

useMount 简化了使用 useEffect 的第二个参数

js 复制代码
    useMount(()=>{
        // dom初始化完成
    })

useUnmount

useUnmount 简化了使用 useEffect 的第二个参数

js 复制代码
    useUnmount(()=>{
        // 组件销毁之前
    })

useMemoizedFn

useMemoizedFn 是一个自定义的 React Hook,用于确定输入输出,useMemoizedFn 接受一个函数,返回一个函数,该函数会根据输入参数变化而变化,避免函数在不必要的情况下更新

js 复制代码
    // useMemoizedFn函数里面使用能拿到最新的b值
    const getNewB = useMemoizedFn(() => {
        return b + 1
    })

useImperativeHandle

自定义暴露给父组件的实例值

js 复制代码
import React, { useRef, useImperativeHandle, forwardRef } from 'react';

const FancyInput = forwardRef((props, ref) => {
  const inputRef = useRef();

  // 自定义暴露给父组件的实例值
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    },
    clear: () => {
      inputRef.current.value = '';
    }
  }));

  return <input ref={inputRef} {...props} />;
});

function Parent() {
  const fancyRef = useRef();

  const handleFocus = () => {
    fancyRef.current.focus();
  };
  const handleClear = () => {
    fancyRef.current.clear();
  };

  return (
    <>
      <FancyInput ref={fancyRef} />
      <button onClick={handleFocus}>聚焦</button>
      <button onClick={handleClear}>清空</button>
    </>
  );
}

useInterval

useInterval 会返回一个函数,实现原理使用了 useEffect,可以直接使用,不用考虑要清除定时器,用法和 setInterval 一致

js 复制代码
    const getTime = useInterval(
    () => {
        console.log('定时器');
    },
    1000,
    { immediate: true },
    );

    getTime()

useDebugValue(不常用)

在开发 React 应用时,我们经常使用 React DevTools 来检查组件的 props、state 和 Hooks 状态。对于自定义 Hook,默认情况下 DevTools 只会显示 Hook 这样一个通用标签,无法直接看到 Hook 内部的具体状态;useDebugValue 允许你为自定义 Hook 添加一个描述性的标签,让调试信息更加直观

js 复制代码
import { useState, useEffect, useDebugValue } from 'react';

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  }, [friendID]);

  // 在 DevTools 中显示调试信息
  useDebugValue(isOnline ? '在线' : '离线');

  return isOnline;
}

自定义hook

在 React 16.8 引入 Hooks 之前,复用逻辑通常使用高阶组件(HOC)或 render props。这些模式虽然有效,但会导致组件树嵌套过深、代码难以理解。自定义 Hook 提供了一种更简洁、更直观的方式来共享逻辑,无需增加组件层级

应用规则:

1、名称必须以 use 开头:这是 React 的约定,用于区分普通函数和 Hook,同时帮助 lint 工具检查 Hook 规则

2、只能在函数组件或其他自定义 Hook 中调用 Hook:不能在普通 JavaScript 函数、循环或条件语句中调用 Hook

3、每次调用都有独立的状态:每次使用自定义 Hook 都会创建独立的 state 和 effect,互不影响

useWindowSize:监听窗口尺寸

js 复制代码
import { useState, useEffect } from 'react';

function useWindowSize() {
  const [size, setSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight
  });

  useEffect(() => {
    const handleResize = () => {
      setSize({
        width: window.innerWidth,
        height: window.innerHeight
      });
    };
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []); // 空依赖表示只在挂载和卸载时执行

  return size;
}

// 使用
function MyComponent() {
  const { width, height } = useWindowSize();
  return <div>窗口大小:{width} x {height}</div>;
}

11、错误边界

在 React 16 之前,组件内部的 JavaScript 错误会导致 React 内部状态被破坏,并在下一次渲染时触发难以理解的错误。React 16 引入了错误边界,使开发者能够优雅地处理局部 UI 的错误,避免整个应用白屏

错误边界是一个类组件,它定义了以下生命周期方法之一(或两者):

static getDerivedStateFromError(error):在子组件抛出错误后调用,用于更新 state 以显示备用 UI;它在渲染阶段调用,因此不允许副作用

componentDidCatch(error, info):在子组件抛出错误后调用,用于记录错误信息(如发送到日志服务);它可以在提交阶段调用,因此允许副作用

js 复制代码
import React from 'react';

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

  static getDerivedStateFromError(error) {
    // 更新 state,下次渲染时显示备用 UI
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    // 你可以将错误记录到错误报告服务
    console.error('Error caught by boundary:', error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      // 自定义备用 UI
      return <h1>出错了!请稍后重试。</h1>;
    }
    return this.props.children;
  }
}

// 使用
<ErrorBoundary>
  <MyComponent />
</ErrorBoundary>

12、redux(状态管理)

Redux 是一个可预测的 JavaScript 状态容器,通常与 React 等视图库一起使用,用于管理应用中的全局状态。它提供了一种集中式存储和管理状态的方式,并遵循严格的规则来更新状态,使状态变化可追踪、可调试;

1、为什么需要 Redux?

在复杂的前端应用中,组件间共享状态可能变得混乱:

多个组件需要访问同一份数据(如用户信息、主题、购物车)。

组件嵌套层级深,通过 props 逐层传递(prop drilling)导致代码冗余。

状态更新逻辑分散在多个组件中,难以追踪和维护。

Redux 通过将状态集中管理,组件可以随时访问和更新全局状态,同时保持状态变化的可预测性

2、核心概念

1、Store(存储)

单一的 JavaScript 对象树,保存整个应用的状态

通过 createStore 创建,提供 getState、dispatch、subscribe 等方法

2、Action(动作)

描述"发生了什么"的普通对象,必须包含 type 字段(字符串常量),可选携带数据(payload)

例如:{ type: 'ADD_TODO', payload: { text: '学习 Redux' } }

3、Reducer(归约器)

纯函数,接收当前状态和 action,返回新状态

签名:(previousState, action) => newState

必须纯函数:不能修改原状态,只能返回新对象;不能有副作用(如 API 调用)

3、三大原则

1、单一数据源:整个应用的全局状态存储在一个对象树中,放在唯一的 Store 中

2、状态只读:唯一改变状态的方式是触发 action,而不是直接修改状态

3、使用纯函数进行修改:Reducer 必须是纯函数,确保状态更新的可预测性

4、数据流(单向)

Redux 的数据流是严格的单向流动,清晰且易于调试:

1、用户交互或事件触发 store.dispatch(action)

2、Store 将当前的 state 和 action 传递给 reducer 函数

3、Reducer 根据 action.type 计算并返回新的 state

4、Store 保存新 state,并通知所有订阅者(如 React 组件)更新

5、旧版本示例(类版本)

1、定义 Action Types 和 Action Creators

js 复制代码
// actions/types.js
export const INCREMENT = 'INCREMENT';
export const DECREMENT = 'DECREMENT';

// actions/counter.js
import { INCREMENT, DECREMENT } from './types';
export const increment = () => ({
  type: INCREMENT
});
export const decrement = () => ({
  type: DECREMENT
});

2、编写 Reducer

Reducer 是纯函数,根据 action 类型返回新状态

js 复制代码
// reducers/counter.js
import { INCREMENT, DECREMENT } from '../actions/types';

const initialState = {
  value: 0
};

export default function counterReducer(state = initialState, action) {
  switch (action.type) {
    case INCREMENT:
      return { ...state, value: state.value + 1 };
    case DECREMENT:
      return { ...state, value: state.value - 1 };
    default:
      return state;
  }
}

如果有多个 reducer,可以使用 combineReducers 合并

js 复制代码
// reducers/index.js
import { combineReducers } from 'redux';
import counterReducer from './counter';

export default combineReducers({
  counter: counterReducer
});

3、创建 Store

使用 createStore 创建 Redux store,并传入根 reducer

js 复制代码
// store.js
import { createStore } from 'redux';
import rootReducer from './reducers';

const store = createStore(rootReducer);

export default store;

4、组件中使用

类组件通过 connect 函数获取 Redux 的状态和 actions,并通过 props 使用

js 复制代码
// components/Counter.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { increment, decrement } from '../actions/counter';

class Counter extends Component {
  render() {
    const { value, increment, decrement } = this.props;
    return (
      <div>
        <h2>Counter: {value}</h2>
        <button onClick={increment}>+</button>
        <button onClick={decrement}>-</button>
      </div>
    );
  }
}

// 将 Redux 状态映射到组件的 props
const mapStateToProps = (state) => ({
  value: state.counter.value  // 注意这里 state.counter 对应 combineReducers 中的 key
});

// 将 action creators 映射到组件的 props,自动绑定 dispatch
const mapDispatchToProps = {
  increment,
  decrement
};

// 使用 connect 创建连接后的组件
export default connect(mapStateToProps, mapDispatchToProps)(Counter);

6、中间件(Middleware)

Redux 中间件提供了一种扩展 Redux 的方式,让你能够在 action 被派发到达 reducer 之前或之后执行额外的逻辑(如日志记录、异步处理、崩溃报告等)

1、应用:手写一个简单的日志中间件

js 复制代码
// loggerMiddleware.js 日志中间件
const loggerMiddleware = store => next => action => {
  console.log(' Dispatching:', action);
  const result = next(action); // 调用下一个中间件或 reducer
  console.log(' Next state:', store.getState());
  return result; // 通常返回 action 本身(由 next 返回)
};
export default loggerMiddleware;

// 使用中间件
import { createStore, applyMiddleware } from 'redux';
import rootReducer from './reducers';
import loggerMiddleware from './loggerMiddleware';
const store = createStore(
  rootReducer,
  applyMiddleware(loggerMiddleware)
);

2、redux thunk

Redux Thunk 是一个中间件,它扩展了 dispatch 的功能,允许 action creator 返回一个函数(称为 thunk)而不是一个普通对象

它解决了什么问题?

支持异步 action:让你可以在 action creator 中编写异步代码,而不必把副作用写在组件里。

访问 getState:在异步操作中可以根据当前状态做决策(如避免重复请求)。

组合与复用:可以将多个 action 组合成一个 thunk,实现更复杂的业务逻辑。

保持 action creator 纯净:虽然 thunk 函数本身有副作用,但它依然通过 dispatch 来触发真正的 action,保持了 action 对象的纯净性

actions/userActions.js

js 复制代码
import {
  FETCH_USERS_REQUEST,
  FETCH_USERS_SUCCESS,
  FETCH_USERS_FAILURE
} from './types';

// 普通 action creators
export const fetchUsersRequest = () => ({
  type: FETCH_USERS_REQUEST
});

export const fetchUsersSuccess = (users) => ({
  type: FETCH_USERS_SUCCESS,
  payload: users
});

export const fetchUsersFailure = (error) => ({
  type: FETCH_USERS_FAILURE,
  payload: error
});

// Thunk action creator:返回一个函数,用于处理异步
export const fetchUsers = () => {
  return async (dispatch, getState) => {
    dispatch(fetchUsersRequest()); // 开始请求,设置 loading 状态
    try {
      const response = await fetch('https://jsonplaceholder.typicode.com/users');
      const data = await response.json();
      dispatch(fetchUsersSuccess(data)); // 请求成功,更新用户数据
    } catch (error) {
      dispatch(fetchUsersFailure(error.message)); // 请求失败,记录错误
    }
  };
};

reducers/userReducer.js

js 复制代码
import {
  FETCH_USERS_REQUEST,
  FETCH_USERS_SUCCESS,
  FETCH_USERS_FAILURE
} from '../actions/types';

const initialState = {
  loading: false,
  users: [],
  error: ''
};

const userReducer = (state = initialState, action) => {
  switch (action.type) {
    case FETCH_USERS_REQUEST:
      return {
        ...state,
        loading: true
      };
    case FETCH_USERS_SUCCESS:
      return {
        loading: false,
        users: action.payload,
        error: ''
      };
    case FETCH_USERS_FAILURE:
      return {
        loading: false,
        users: [],
        error: action.payload
      };
    default:
      return state;
  }
};

export default userReducer;

store.js

js 复制代码
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';

const store = createStore(rootReducer, applyMiddleware(thunk));

export default store;
相关推荐
重庆穿山甲1 小时前
身份证照片自动裁剪(OpenCV 四边形检测 + 透视矫正)
前端·后端
跟着珅聪学java1 小时前
Electron + Vue 现代化“新品展示“和“快捷下单“菜单
开发语言·前端·javascript
何贤1 小时前
用 Three.js 写了一个《我的世界》,结果老外差点给我众筹做游戏?
前端·开源·three.js
踩着两条虫2 小时前
AI 驱动的 Vue3 应用开发平台 深入探究(七):双向代码转换之 Vue源码到DSL解析
前端·vue.js·ai编程
专业流量卡2 小时前
用ai去看源码
前端·react.js
踩着两条虫2 小时前
AI 驱动的 Vue3 应用开发平台 深入探究(六):双向代码转换之DSL到Vue代码生成
前端·vue.js·ai编程
前端老兵AI2 小时前
React vs Vue 2026年怎么选?9年前端的真实建议
vue.js·react.js
Wect2 小时前
React 中的双缓存 Fiber 树机制
前端·react.js·面试
天才熊猫君2 小时前
Vue 3 中 Watch 的陷阱:为什么异步操作后创建的监听会泄漏?
前端·javascript