React 是一个用于构建用户界面的 JavaScript 库,由 Facebook 开发并维护。它的核心理念是“组件化”,即将用户界面拆分为可重用的组件。

React 的组件通常使用 JSX(JavaScript XML)。JSX 是一种 JavaScript 语法扩展,允许开发者在 JavaScript 代码中编写类似 HTML 的结构。

1、初识react

1.1 常用命令

首先安装 Nodejs,然后执行下面命令安装 react

npm i -g create-react-app
create-react-app myreact # 创建react项目

npm start # 在项目中输入,启动项目

1.2 项目结构

项目创建后有很多文件,一般我们只需保留下面的就行.

  • index.html:应用的主 HTML 文件,React 应用的根组件通常挂载在此文件中的一个
    元素上。
  • index.css:全局样式文件。
  • index.js:应用的入口文件,负责渲染根组件并将其挂载到 index.html 中的 DOM。
  • package.json: 包含项目的元数据(如名称、版本、描述等)、依赖包列表和脚本命令(如 startbuildtest)。
my-app/
├── node_modules/
├── public/
│   ├── favicon.ico
|   ├── index.html
├── src/
│   ├── index.css
│   ├── index.js
├── .gitignore
├── package.json
├── README.md
└── yarn.lock / package-lock.json

2、基本知识点

1. extends Component

在 React 中,extends Component 是用来创建类组件的方式。React 的类组件通过继承 React.Component 来获得组件的基本功能,如状态管理和生命周期方法。以下是一个简单的例子:

import React, { Component } from 'react';

class MyComponent extends Component {
  render() {
    return <h1>Hello, World!</h1>;
  }
}
  • Component:是 React 提供的基类,包含了生命周期方法(如 componentDidMountcomponentDidUpdate 等)和其他基本功能。

  • render 方法:每个类组件必须实现的一个方法,用于描述 UI 如何渲染。每当组件的 stateprops 更新时,render 方法会被调用。

    render() {
      return (
        <div>
          <h1>Hello, {this.props.name}!</h1>
        </div>
      );
    }
    
    • 返回值render 方法可以返回一个单一的元素、多个元素(需要用一个父元素包裹)或者 null

2. React.Fragment

React.Fragment 是一个用于分组多个子元素而不添加额外 DOM 节点的工具。通常,React 要求每个组件必须返回一个单一的根元素,使用 Fragment 可以解决这个问题。示例:

import React from 'react';

class MyComponent extends Component {
  render() {
    return (
      <React.Fragment>
        <h1>Title</h1>
        <p>Some content.</p>
      </React.Fragment>
    );
  }
}
  • 语法简化:可以用 <></> 来简化 Fragment 的使用:

3. className

在 React 中,使用 className 属性来指定一个或多个 CSS 类,而不是使用 HTML 的 class 属性。这是因为 class 是 JavaScript 中的保留关键字,React 采用 className 来避免冲突。

示例:

function MyComponent() {
  return <div className="my-class">Hello, World!</div>;
}
.my-class {
  color: blue;
  font-size: 20px;
}
动态类名
function MyComponent({ isActive }) {
  return (
    <div className={`my-class ${isActive ? 'active' : ''}`}>
      Hello, World!
    </div>
  );
}

在这个例子中,如果 isActivetruediv 将具有 active 类。

4. {} 的用法

在 JSX 中,{} 用于表示 JavaScript 表达式。你可以在其中嵌入变量、函数调用和其他表达式。

示例:

const name = "Alice";

function Greeting() {
  return <h1>Hello, {name}!</h1>; // 使用 {} 插入变量
}

5 jsx 中的 css

1. 基本语法

在 JSX 中,内联样式需要使用 JavaScript 对象的形式定义。具体来说:

  • 样式属性使用驼峰命名法(camelCase)。
  • 属性值通常为字符串或数字(需要附加单位时,使用字符串)。
1.1 示例代码
const MyComponent = () => {
    const style = {
        color: 'blue',
        backgroundColor: 'lightgray',
        padding: '10px',
        fontSize: '16px',
        border: '1px solid black'
    };

    return <div style={style}>这是一个内联样式的组件</div>;
};

在这个示例中,我们定义了一个样式对象 style,并将其应用到 <div> 元素中。

2. 动态样式

内联样式的一个主要优势是可以很容易地根据组件的状态或属性动态更新样式。例如,可以根据 state 的值来改变样式:

import React, { useState } from 'react';

const DynamicStyleComponent = () => {
    const [isActive, setIsActive] = useState(false);

    const style = {
        color: isActive ? 'white' : 'black',
        backgroundColor: isActive ? 'blue' : 'gray',
        padding: '10px',
        cursor: 'pointer'
    };

    return (
        <div 
            style={style} 
            onClick={() => setIsActive(!isActive)}
        >
            {isActive ? '激活状态' : '非激活状态'}
        </div>
    );
};

在这个示例中,点击 <div> 会切换其状态,从而改变文字和背景颜色。

3. 优缺点
优点
  • 简单直接:可以快速应用样式,尤其是动态样式时。
  • 无类名冲突:因为样式是局部的,不会受到其他样式的影响。
缺点
  • 不可复用:无法将样式应用于多个组件,难以维护。
  • 性能问题:每次渲染都会创建新的对象,可能会影响性能,特别是在频繁更新的组件中。
  • 不支持伪类和媒体查询:无法使用 CSS 的伪类(如 :hover)和媒体查询,限制了样式的灵活性。

3、Componets 写法

1. React 组件的基本概念

React 组件是构成 React 应用的基本单元。它们可以是类组件或函数组件。类组件使用 ES6 类来定义,而函数组件则是简单的 JavaScript 函数。

1.1 类组件

类组件通常用于需要管理状态或使用生命周期方法的场景。

import React, { Component } from 'react';

class MyComponent extends Component {
    // 构造函数用于初始化 state
    constructor(props) {
        super(props);
        this.state = {
            count: 0
        };
    }
    // 上面 constructor 这块的代码可以只写 state {count : 0}, 
    // 在类组件中,可以直接在类的主体中定义 state,而不必在构造函数中初始化。
    // 这种语法会自动将 state 绑定到实例上,因此不需要显式调用 super(props)。

    // 增加 count 的方法
    increment = () => {
        this.setState({ count: this.state.count + 1 });
    };

    render() {
        return (
            <div>
                <h1>{this.state.count}</h1>
                <button onClick={this.increment}>增加</button>
            </div>
        );
    }
}

export default MyComponent;
1.2 函数组件

函数组件是无状态的,可以使用 React Hooks 来管理状态和副作用。

import React, { useState } from 'react';

const MyComponent = () => {
    const [count, setCount] = useState(0); // useState Hook

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

    return (
        <div>
            <h1>{count}</h1>
            <button onClick={increment}>增加</button>
        </div>
    );
};

export default MyComponent;

2. State 的管理

state 是组件的本地状态,用于存储组件的动态数据。当状态改变时,React 会自动重新渲染组件。

2.1 设置初始状态

在类组件中,初始状态通常在构造函数中定义;在函数组件中,可以使用 useState Hook。

2.2 修改状态

使用 this.setState(类组件)或状态更新函数(函数组件)来更新状态。重要的是,状态更新是异步的。

// 类组件
this.setState({ key: value });

// 函数组件
setCount(newCount);

3. 遍历生成元素

在状态中存储一个数组,然后通过 .map() 方法遍历生成多个元素。

3.1 类组件示例
class ListComponent extends Component {
    constructor(props) {
        super(props);
        this.state = {
            items: ['苹果', '香蕉', '橙子']
        };
    }

    render() {
        return (
            <ul>
                {this.state.items.map((item, index) => (
                    <li key={index}>{item}</li>
                ))}
            </ul>
        );
    }
}
3.2 函数组件示例
const ListComponent = () => {
    const [items, setItems] = useState(['苹果', '香蕉', '橙子']);

    return (
        <ul>
            {items.map((item, index) => (
                <li key={index}>{item}</li>
            ))}
        </ul>
    );
};

4. 函数传参

4.1 传递参数 e
class MyComponent extends Component {
  handleClick = (e) => {
    console.log(e.target);
  };

  render() {
    return (
      <button onClick={this.handleClick}>	// 传递参数 e
        Click me
      </button>
    );
  }
}
4.2 传递自定义参数
class MyComponent extends Component {
  handleClick = (param) => {
    console.log('Button clicked!', param);
  };

  render() {
    return (
      <button onClick={() => this.handleClick('Hello')}>	// 传递自定义参数
        Click me
      </button>
    );
  }
}
4.3 传递参数 e 和 自定义参数
class MyComponent extends Component {
  handleClick = (param, e) => {
    console.log('Button clicked!', param);
  };

  render() {
    return (
      <button onClick={(e) => this.handleClick('Hello', e)}>	// 传递参数
        Click me
      </button>
    );
  }
}

5. 添加、删除和更新状态

可以通过按钮或其他交互方式来添加、删除或更新状态中的元素。

5.1 添加元素
// 在类组件中
addItem = () => {
    this.setState(prevState => ({
        items: [...prevState.items, '新水果']
    }));
};

// 在函数组件中
const addItem = () => {
    setItems([...items, '新水果']);
};
5.2 删除元素
// 在类组件中
removeItem = index => {
    this.setState(prevState => ({
        items: prevState.items.filter((_, i) => i !== index)
    }));
};

// 在函数组件中
const removeItem = index => {
    setItems(items.filter((_, i) => i !== index));
};

6. 受控表单绑定

import React, { useState } from 'react';

function ControlledForm() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    alert(`Name: ${name}, Email: ${email}`);
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>
          Name:
          <input
            type="text"
            value={name}	// 把 name 赋值给value
            onChange={(e) => setName(e.target.value)}		// name 绑定上target.value
          />
        </label>
      </div>
      <div>
        <label>
          Email:
          <input
            type="email"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
          />
        </label>
      </div>
      <button type="submit">Submit</button>
    </form>
  );
}

export default ControlledForm;

7. 获取dom元素

1. 函数组件

可以通过 useRef 创建 refs:

import React, { useRef } from 'react';

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

  const handleFocus = () => {
    inputRef.current.focus(); // 获取到的 DOM 元素调用 focus 方法
  };

  return (
    <div>
      <input type="text" ref={inputRef} />
      <button onClick={handleFocus}>Focus the input</button>
    </div>
  );
}

export default FocusInput;
  • useRef: 创建一个 ref,用于存储对 DOM 元素的引用。
  • ref 属性: 将 ref 赋值给要获取的元素,例如 <input>
  • 访问 DOM 元素: 通过 inputRef.current 来访问 DOM 元素。
2. 类组件

可以通过 React.createRef() 创建 refs:

import React, { Component } from 'react';

class FocusInput extends Component {
  constructor(props) {
    super(props);
    this.inputRef = React.createRef();
  }

  handleFocus = () => {
    this.inputRef.current.focus();
  };

  render() {
    return (
      <div>
        <input type="text" ref={this.inputRef} />
        <button onClick={this.handleFocus}>Focus the input</button>
      </div>
    );
  }
}

export default FocusInput;

4、组件之间传递数据

子组件接收到的 props 是不可变的。如果需要修改 props 中的数据,应该通过父组件来管理状态并传递更新的值。

1 - 4 都是 父传子, 5 是 子传父

1. 传递常见数据类型(类组件写法)

在父组件的 state 中存储基本数据类型,并通过 props 传递给子组件。

父组件

import React, { Component } from 'react';
import ChildComponent from './ChildComponent';

class ParentComponent extends Component {
  state = {
  	// 基本数据类型
    message: 'Hello from Parent!',
    numberValue: 42,
    boolValue: true,
    
    user: {	// 对象
      name: 'Alice',
      age: 30,
    },
    
  };

  handleAlert = () => {	// 方法
    alert('Hello from Parent!');
  };

  updateBoolValue = () => {
    this.setState(prevState => ({ boolValue: !prevState.boolValue }));
  };

  render() {
    return (
      <div>
        <h1>Parent Component</h1>
        <ChildComponent
          message={this.state.message}
          number={this.state.numberValue}
          isTrue={this.state.boolValue}
          
          user={this.state.user}	// 对象
          showAlert={this.handleAlert}	// 方法
        />
        <button onClick={this.updateBoolValue}>Toggle Boolean</button>
      </div>
    );
  }
}

export default ParentComponent;

子组件

import React, { Component } from 'react';

class ChildComponent extends Component {
  render() {
    return (
      <div>
        <h2>Child Component</h2>
        <p>{this.props.message}</p>
        <p>Number: {this.props.number}</p>
        <p>Boolean: {this.props.isTrue ? 'True' : 'False'}</p>
        
        <ChildComponent user={this.state.user} />	// 对象
      </div>
    );
  }
}

export default ChildComponent;

2. 传递常见数据类型(函数组件写法)

父组件

import React, { useState } from 'react';
import ChildComponent from './ChildComponent';

const ParentComponent = () => {
  const [message] = useState('Hello from Parent!');
  const [numberValue] = useState(42);
  const [boolValue, setBoolValue] = useState(true);
  const [user] = useState({
    name: 'Alice',
    age: 30,
  });

  const handleAlert = () => {
    alert('Hello from Parent!');
  };

  const updateBoolValue = () => {
    setBoolValue(prev => !prev);
  };

  return (
    <div>
      <h1>Parent Component</h1>
      <ChildComponent
        message={message}
        number={numberValue}
        isTrue={boolValue}
        user={user}
        showAlert={handleAlert}
      />
      <button onClick={updateBoolValue}>Toggle Boolean</button>
    </div>
  );
};

export default ParentComponent;

子组件

import React from 'react';

const ChildComponent = ({ message, number, isTrue, user }) => {
  return (
    <div>
      <h2>Child Component</h2>
      <p>{message}</p>
      <p>Number: {number}</p>
      <p>Boolean: {isTrue ? 'True' : 'False'}</p>
      <p>User: {user.name}, Age: {user.age}</p> {/* 对象 */}
    </div>
  );
};

export default ChildComponent;

说明

  • 使用 useState 来管理状态。
  • 通过解构 props 直接在函数参数中获取需要的值。
  • 保持功能和逻辑不变,实现了相同的父子组件传值。

3. 传递多个 HTML 结构(类组件)

父组件将多个 JSX 元素传递给子组件,通过 props.children 访问这些元素。

import React, { Component } from 'react';
import ChildComponent from './ChildComponent';

class ParentComponent extends Component {
  render() {
    return (
      <div>
        <h1>Parent Component</h1>
        <ChildComponent>	//  把 html 写在组件子组件当中
          <p>This is the first custom content!</p>
          <p>This is the second custom content!</p>
          <h3>This is a header as custom content!</h3>
        </ChildComponent>
      </div>
    );
  }
}

export default ParentComponent;

子组件

在子组件中,我们可以通过 React.Children.map 或者 this.props.children 分别处理传递过来的多个元素。

import React, { Component } from 'react';

class ChildComponent extends Component {
  render() {
    return (
      <div>
        <h2>Child Component</h2>	// children数组中包含了父组件传递的 html
        {React.Children.map(this.props.children, (child, index) => (
          <div key={index}>{child}</div>
        ))}
      </div>
    );
  }
}

export default ChildComponent;

4. 传递多个 HTML 结构(函数组件)

父组件

import React from 'react';
import ChildComponent from './ChildComponent';

const ParentComponent = () => {
  return (
    <div>
      <h1>Parent Component</h1>
      <ChildComponent>
        <p>This is the first custom content!</p>
        <p>This is the second custom content!</p>
        <h3>This is a header as custom content!</h3>
      </ChildComponent>
    </div>
  );
};

export default ParentComponent;

子组件

import React from 'react';

const ChildComponent = ({ children }) => {
  return (
    <div>
      <h2>Child Component</h2>
      {React.Children.map(children, (child, index) => (
        <div key={index}>{child}</div>
      ))}
    </div>
  );
};

export default ChildComponent;

说明

  • 父组件: 使用函数组件,并将多个 JSX 元素作为子组件的内容传递。
  • 子组件: 通过解构 props 获取 children,并使用 React.Children.map 迭代处理每个子元素。

5. 子传父

父组件

import React, { useState } from 'react';
import ChildComponent from './ChildComponent';

const ParentComponent = () => {
  const [dataFromChild, setDataFromChild] = useState('');

  const handleDataChange = (data) => {
    setDataFromChild(data);
  };

  return (
    <div>
      <h1>Parent Component</h1>
      <p>Data from Child: {dataFromChild}</p>
      <ChildComponent onDataChange={handleDataChange} />
    </div>
  );
};

export default ParentComponent;

子组件

import React from 'react';

const ChildComponent = ({ onDataChange }) => {
  const sendDataToParent = () => {
    const data = 'Hello from Child!';
    onDataChange(data); // 调用父组件传来的函数
  };

  return (
    <div>
      <h2>Child Component</h2>
      <button onClick={sendDataToParent}>Send Data to Parent</button>
    </div>
  );
};

export default ChildComponent;

说明

  1. 父组件: ParentComponent 使用 useState 来存储从子组件接收到的数据,并定义一个 handleDataChange 函数来更新状态。
  2. 子组件: ChildComponent 接收 onDataChange 函数作为 prop,点击按钮时调用该函数将数据发送给父组件。

5、生命周期

  • 挂载阶段

    • constructor()
    • render()
    • componentDidMount()
  • 更新阶段

    • getDerivedStateFromProps()
    • shouldComponentUpdate()
    • render()
    • componentDidUpdate()
  • 卸载阶段

    • componentWillUnmount()

1. 挂载(Mount)

当组件被创建并插入到 DOM 中时,进入挂载阶段。执行顺序如下:

  1. constructor():构造函数用于初始化状态和绑定方法。
  2. render():返回要渲染的 JSX,决定组件的结构。
  3. componentDidMount():组件挂载后调用,适合进行 API 请求或添加事件监听。

2. 更新(Update)

当组件的状态或属性发生变化时,组件会重新渲染,进入更新阶段。执行顺序如下:

  1. render():重新渲染组件。
  2. getDerivedStateFromProps(nextProps, prevState):在渲染前调用,可以根据新 props 更新 state。
  3. shouldComponentUpdate(nextProps, nextState):用于控制组件是否重新渲染,返回 truefalse
  4. componentDidUpdate(prevProps, prevState):组件更新后调用,可以根据上一个状态或属性执行副作用。

3. 卸载(Unmount)

当组件从 DOM 中移除时,进入卸载阶段。执行顺序如下:

  1. componentWillUnmount():组件卸载前调用,适合清理资源(如取消网络请求或移除事件监听)。

4. 使用 Hook 的方式

在函数组件中,可以使用 Hook 来实现类似的生命周期效果:

  • useEffect:相当于 componentDidMountcomponentDidUpdate,可以用于处理副作用。
import React, { useEffect, useState } from 'react';

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

  useEffect(() => {
    // 组件挂载时执行
    console.log('Component mounted');

    // 组件更新时执行
    return () => {
      // 组件卸载时清理
      console.log('Component unmounted');
    };
  }, [count]); // 依赖数组,count 变化时执行

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

6、 路由

1. 基础概念

路由(Routing):在单页应用(SPA)中,路由用于管理不同视图或页面之间的导航。当用户点击链接或按钮时,路由会拦截这些操作,并根据 URL 的变化加载相应的组件,而不需要重新加载整个页面。

React Router:这是最流行的 React 路由库,它提供了灵活的路由功能。React Router 通过组件化的方式来定义和管理路由。

2. 常用组件

React Router 提供了几个核心组件,下面是一些常用的组件及其用途:

  • BrowserRouter:这个组件提供了基于 HTML5 的历史记录的路由。它通常包裹在应用的根组件外部。

    import { BrowserRouter } from 'react-router-dom';
    
    function App() {
      return (
        <BrowserRouter>
          {/* 其他组件 */}
        </BrowserRouter>
      );
    }
    
  • Route:用于定义路由的组件。它接受 path 属性来指定匹配的 URL,以及 element 属性来指定渲染的组件。

    import { Route } from 'react-router-dom';
    
    <Route path="/about" element={<About />} />
    
  • Routes:用来包裹多个 Route 组件,确保只有一个匹配的路由被渲染。

    import { Routes } from 'react-router-dom';
    
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/about" element={<About />} />
    </Routes>
    
  • Link:用于在应用中创建导航链接的组件。它类似于 HTML 的 <a> 标签,但使用的是客户端路由。

    import { Link } from 'react-router-dom';
    
    <Link to="/about">About</Link>
    
  • Navigate:用于在组件中进行重定向。

    import { Navigate } from 'react-router-dom';
    
    <Navigate to="/home" />
    

3. 路由配置

配置路由通常涉及创建一个路由表,定义各个路由及其对应的组件。以下是一个简单的示例,展示如何在 React 应用中配置路由。

import React from 'react';
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';

// 导入页面组件
import Home from './Home';
import About from './About';
import NotFound from './NotFound';

function App() {
  return (
    <BrowserRouter>
      <nav>
        <Link to="/">Home</Link>
        <Link to="/about">About</Link>
      </nav>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        {/* 404 页面 */}
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}

export default App;

4. 嵌套路由 及 路由守卫、404

嵌套路由允许你在某个父路由的组件内部定义子路由。这样,你可以在父路由的页面中渲染子路由对应的内容。

import { Routes, Route, Link } from 'react-router-dom';

function Users() {
  return (
    <div>
      <h2>Users</h2>
      <Link to="1">User 1</Link>
      <Link to="2">User 2</Link>
      <Routes>
        <Route path=":userId" element={<UserProfile />} />
      </Routes>
    </div>
  );
}

function UserProfile() {
  const { userId } = useParams();
  return <h3>User Profile for User ID: {userId}</h3>;
}

路由守卫

路由保护可以确保只有特定条件下(例如用户登录后)才能访问某些路由。可以通过创建一个高阶组件(HOC)来实现。

function ProtectedRoute({ element, isAuthenticated }) {
  return isAuthenticated ? element : <Navigate to="/login" />;
}

// 使用
<Routes>
  <Route path="/dashboard" element={<ProtectedRoute element={<Dashboard />} isAuthenticated={isLoggedIn} />} />
</Routes>

404 页面处理

可以使用通配符路由 * 来处理未匹配的路径,并显示 404 页面。

<Route path="*" element={<NotFound />} />

5. 动态路由(match)

动态路由允许你在路径中使用参数,这样可以在 URL 中捕获动态值。例如,展示用户信息的页面。

<Route path="/users/:userId" element={<UserProfile />} />
类组件中

使用 URL 参数,可以使用 withRouter 高阶组件来获取路由的 props,包括 matchlocationhistory 对象。

使用 withRouter 获取 URL 参数

import React from 'react';
import { withRouter } from 'react-router-dom';

class UserProfile extends React.Component {
  render() {
    // 从 props 中获取 match 对象
    const { match } = this.props;
    const userId = match.params.userId; // 获取 URL 参数

    return <div>User ID: {userId}</div>;
  }
}

// 使用 withRouter 将 UserProfile 包装起来
export default withRouter(UserProfile);

关键点

  • withRouter 是一个高阶组件,它将路由的相关信息作为 props 传递给被包装的组件。
  • match.params 中包含了 URL 中的动态参数,像这里的 userId

确保在路由配置中使用 UserProfile 组件时,像这样设置路由:

<Route path="/users/:userId" element={<UserProfile />} />
函数组件

直接使用 useParams 来获取 URL 参数:

import { useParams } from 'react-router-dom';

function UserProfile() {
  const { userId } = useParams();
  return <div>User ID: {userId}</div>;
}

6. 获取 修改 查询字符串(location)

https://example.com/users?userId=123&userName=Alice

1. 类组件获取查询字符串参数

在类组件中,我们通常使用 withRouter 高阶组件来访问路由相关的信息,包括查询字符串。

示例代码

import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';

class QueryParamsClassComponent extends Component {
  render() {
    const { location } = this.props; // 从 props 中获取 location 对象
    const searchParams = new URLSearchParams(location.search); // 创建 URLSearchParams 对象

    // 获取具体的参数
    const userId = searchParams.get('userId'); // 获取 userId 参数
    const name = searchParams.get('name'); // 获取 name 参数
    // searchParams。set('userId', '456'); // 修改或添加 userId 参数
    // searchParams.set('name', 'Alice'); // 修改或添加 name 参数

    return (
      <div>
        <h1>Query Parameters</h1>
        <p>User ID: {userId}</p>
        <p>Name: {name}</p>
      </div>
    );
  }
}

export default withRouter(QueryParamsClassComponent); // 使用 withRouter 包裹类组件

代码解析

  1. 引入 withRouter:首先需要引入 withRouter,它使得组件可以访问路由的 props。
  2. 访问 location 对象:在 render 方法中,从 this.props 中获取 location 对象。
  3. 使用 URLSearchParams
    • new URLSearchParams(location.search) 创建一个 URLSearchParams 对象,location.search 包含查询字符串(包括 ?)。
    • 通过 get 方法提取具体的参数值,例如 userIdname
  4. 渲染参数:在组件的 JSX 中渲染这些参数。
2. 函数组件获取查询字符串参数

在函数组件中,我们可以使用 useLocation Hook 来获取路由信息,包括查询字符串。

示例代码

import React from 'react';
import { useLocation } from 'react-router-dom';

const QueryParamsFunctionComponent = () => {
  const location = useLocation(); // 使用 useLocation 获取 location 对象
  const searchParams = new URLSearchParams(location.search); // 创建 URLSearchParams 对象

  // 获取具体的参数
  const userId = searchParams.get('userId'); // 获取 userId 参数
  const name = searchParams.get('name'); // 获取 name 参数
  //searchParams.get('userId'); // 获取 userId 参数
  //searchParams.get('name'); // 获取 name 参数

  return (
    <div>
      <h1>Query Parameters</h1>
      <p>User ID: {userId}</p>
      <p>Name: {name}</p>
    </div>
  );
};

export default QueryParamsFunctionComponent; // 直接导出函数组件

代码解析

  1. 引入 useLocation:引入 useLocation Hook。
  2. 调用 useLocation:在函数组件中调用 useLocation(),它返回当前路由的 location 对象。
  3. 使用 URLSearchParams
    • new URLSearchParams(location.search) 创建一个 URLSearchParams 对象,用于解析查询字符串。
    • 使用 get 方法提取需要的参数值。
  4. 渲染参数:在组件的 JSX 中渲染这些参数。

7、编程导航 history

1. history 对象的概念
  • 历史记录history 对象是一个 JavaScript 对象,它表示浏览器的会话历史。每当用户访问一个新的 URL 时,浏览器会将该 URL 添加到历史记录中。
  • 导航:通过 history 对象,你可以编程式地导航到不同的页面,而不是仅仅依赖于用户点击链接。
2. history 对象的常用方法

history 对象提供了多种方法来处理导航和历史记录。以下是一些常用的方法:

push(path, [state])

  • 功能:在历史记录栈中添加一个新的条目,导航到指定的路径。
  • 参数
    • path:要导航到的路径。
    • state(可选):与新路径关联的状态对象,这个对象可以在目标页面通过 location.state 访问。

示例

this.props.history.push('/home'); // 导航到 /home
this.props.history.push('/profile', { from: 'home' }); // 导航到 /profile,并传递状态

replace(path, [state])

  • 功能:替换当前的历史记录条目,导航到指定的路径,而不是添加一个新的条目。
  • 参数:同 push 方法。

示例

this.props.history.replace('/login'); // 用 /login 替换当前的历史条目

go(n)

  • 功能:根据历史记录栈中的位置导航。
  • 参数
    • n:要导航的相对位置(正数表示向前,负数表示向后)。

示例

this.props.history.go(-1); // 返回到上一页
this.props.history.go(2); // 前进两页

goBack() 返回到上一页,等同于 go(-1)

goForward() 前进到下一页,等同于 go(1)

3. 在 React Router 中使用 history

在 React Router 中,history 对象通常是通过 withRouter 高阶组件或在函数组件中通过 useNavigateuseLocation Hook 访问的。

类组件使用 withRouter

import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';

class MyComponent extends Component {
  handleNavigation = () => {
    this.props.history.push('/home'); // 使用 history 导航
  };

  render() {
    return <button onClick={this.handleNavigation}>Go to Home</button>;
  }
}

export default withRouter(MyComponent); // 使用 withRouter 包裹组件

函数组件使用 useNavigate

在 React Router v6 及以上版本中,推荐使用 useNavigate Hook 来获取 navigate 函数。

import React from 'react';
import { useNavigate } from 'react-router-dom';

const MyFunctionComponent = () => {
  const navigate = useNavigate();

  const handleNavigation = () => {
    navigate('/home'); // 使用 navigate 导航
  };

  return <button onClick={handleNavigation}>Go to Home</button>;
};

export default MyFunctionComponent;

8、子组件的路由 Outlet

// App.js
import React from 'react';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import UserProfile from './UserProfile';
import UserSettings from './UserSettings';
import UserActivity from './UserActivity';

const App = () => {
  return (
    <Router>
      <Routes>
        <Route path="/users/*" element={<UserProfile />}>
          <Route path="settings" element={<UserSettings />} />
          <Route path="activity" element={<UserActivity />} />
        </Route>
      </Routes>
    </Router>
  );
};

export default App;

// UserProfile.js
import React from 'react';
import { Link, Outlet } from 'react-router-dom';

const UserProfile = () => {
  return (
    <div>
      <h1>User Profile</h1>
      <nav>
        <ul>
          <li>
            <Link to="settings">Settings</Link>
          </li>
          <li>
            <Link to="activity">Activity</Link>
          </li>
        </ul>
      </nav>
      <Outlet /> {/* 渲染匹配的子路由 */}		// 路由
    </div>
  );
};

export default UserProfile;

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

const UserSettings = () => {
  return <h2>User Settings</h2>;
};

export default UserSettings;

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

const UserActivity = () => {
  return <h2>User Activity</h2>;
};

export default UserActivity;

7、redux 集中状态管理

Redux.createStore 是 Redux 的传统方式,用于创建 Redux store。

Redux.createStore

  1. 导入 Redux:
import { createStore } from 'redux';
  1. 定义初始状态:
const initialState = {
  todos: [],
};
  1. 创建 Reducer:

Reducer 是一个纯函数,接受当前状态和 action,并返回新的状态。

const todoReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return { ...state, todos: [...state.todos, action.payload] };
    default:
      return state;
  }
};
  1. 创建 Store:

使用 createStore 创建 store,并传入 reducer。

const store = createStore(todoReducer);
  1. 使用 Store:

可以使用 store.getState() 获取当前状态,使用 store.dispatch(action) 发送 action。

store.dispatch({ type: 'ADD_TODO', payload: { text: 'Learn Redux' } });
console.log(store.getState());

Redux Toolkit

Redux Toolkit 是官方推荐的 Redux 工具集,它简化了 Redux 的使用,包含了一些开箱即用的工具和最佳实践。使用 Redux Toolkit 可以更容易地管理状态,减少模板代码。

1. 安装

首先,安装 Redux Toolkit 和 React-Redux:

npm install @reduxjs/toolkit react-redux
2. 创建 Slice

使用 createSlice 来定义 state、reducers 和 actions。这样可以简化代码结构。

import { createSlice } from '@reduxjs/toolkit';

const todosSlice = createSlice({
  name: 'todos',
  initialState: [], // 要存的state

  // 使用 createSlice 创建 slice 时,定义的 reducer 函数会自动生成对应的 action creators。
  reducers: {
    addTodo: (state, action) => {	// state中包含所有 initialState 中的数据, 
      state.push(action.payload); // 使用 Immer 库来处理不可变状态
    },
    removeTodo: (state, action) => {
      return state.filter(todo => todo.id !== action.payload);	// payload 包含了调用时传递的参数, 可以是对象
    },
  },
});

// 导出 action
export const { addTodo, removeTodo } = todosSlice.actions;

// 导出 reducer
export default todosSlice.reducer;
3. 配置 Store

使用 configureStore 来创建 store,它会自动添加 Redux DevTools 支持和中间件。

import { configureStore } from '@reduxjs/toolkit';
import todosReducer from './todosSlice';

const store = configureStore({
  reducer: {
    todos: todosReducer,
  },
});

export default store;
4. 连接 React 和 Redux

在应用中使用 Provider 将 store 传递给组件树。

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')
);
5. 使用 Redux State

在组件中使用 useSelectoruseDispatch 来访问状态和发起 action。

import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { addTodo, removeTodo } from './todosSlice';

const TodoList = () => {
  const todos = useSelector(state => state.todos);
  const dispatch = useDispatch();

  const handleAddTodo = () => {
    const text = prompt('Enter todo:');
    const id = Date.now(); // 简单生成 ID
    dispatch(addTodo({ id, text }));
  };

  const handleRemoveTodo = (id) => {
    dispatch(removeTodo(id));
  };

  return (
    <div>
      <h1>Todo List</h1>
      <ul>
        {todos.map(todo => (
          <li key={todo.id}>
            {todo.text}
            <button onClick={() => handleRemoveTodo(todo.id)}>Remove</button>
          </li>
        ))}
      </ul>
      <button onClick={handleAddTodo}>Add Todo</button>
    </div>
  );
};

export default TodoList;
6. 处理异步操作

使用 createAsyncThunk 处理异步请求。

import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

// 异步操作示例
export const fetchTodos = createAsyncThunk('todos/fetchTodos', async () => {
  const response = await fetch('/api/todos');
  return response.json();
});

const todosSlice = createSlice({
  name: 'todos',
  initialState: { items: [], status: 'idle' },
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(fetchTodos.fulfilled, (state, action) => {
        state.items = action.payload;
      });
  },
});

// 导出 reducer
export default todosSlice.reducer;

8、hook

好的,我们来详细讲解一下 React Hooks,包括基本用法、常见的 Hooks 和具体的应用场景。

1、基本用法

React Hooks 是 React 16.8 引入的一种新特性,旨在让函数组件具备类组件的功能,尤其是状态管理和副作用处理。Hooks 的引入使得函数组件更加强大和灵活。

1. 使用 Hooks 的规则
  • 只能在函数组件或自定义 Hooks 中调用。 不可以在 普通 JavaScript 函数类组件 或者 条件语句 中调用。
  • 调用顺序必须一致。 React 会根据调用的顺序来追踪每个 Hook 的状态。

2、常见的 Hooks

1. useState

用于在函数组件中添加状态。

import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0); // count 是状态,setCount 是更新状态的函数

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}
2. useEffect

useEffect(()=> {}, 依赖项)
在这里插入图片描述

用于处理副作用,例如数据获取、订阅等。

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

function DataFetcher() {
  const [data, setData] = useState(null);

  useEffect(() => {
    fetch('https://api.example.com/data')
      .then(response => response.json())
      .then(data => setData(data));

    // 可选的清理函数
    return () => {
      // 例如取消订阅
    };
  }, []); // 依赖数组,空数组意味着仅在组件挂载时执行一次

  return <div>{data ? JSON.stringify(data) : 'Loading...'}</div>;
}
3. useContext

用于在组件之间共享状态,避免逐层传递 props。

import React, { createContext, useContext } from 'react';

const ThemeContext = createContext('light');

function ThemedComponent() {
  const theme = useContext(ThemeContext);

  return <div className={theme}>Current theme: {theme}</div>;
}

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <ThemedComponent />
    </ThemeContext.Provider>
  );
}
4. useReducer

用于在复杂状态逻辑中管理状态,相比 useState 更加灵活。

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:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      Count: {state.count}
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </div>
  );
}
5. 自定义 Hooks

你可以创建自己的 Hooks,复用逻辑。

import { useState, useEffect } from 'react';

function useFetch(url) {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetch(url)
      .then(response => response.json())
      .then(setData)
      .catch(setError);
  }, [url]);

  return { data, error };
}

// 使用自定义 Hook
function DataFetcher({ url }) {
  const { data, error } = useFetch(url);

  if (error) return <div>Error: {error.message}</div>;
  return <div>{data ? JSON.stringify(data) : 'Loading...'}</div>;
}

3、具体的应用场景

  1. 数据获取: 使用 useEffectuseState 来从 API 获取数据。
  2. 表单管理: 使用 useState 来管理输入框的状态,并处理提交。
  3. 动画: 使用 useEffect 来设置动画的入场和离场。
  4. 订阅: 在 useEffect 中添加订阅,返回一个清理函数以取消订阅。
  5. 组合逻辑: 使用自定义 Hooks 来提取组件之间共享的逻辑。
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐