React知识点速刷

186 阅读4分钟

React 是一个用于构建用户界面的 JavaScript 库。React 是用户界面(UI)的一部分,不是一个完整的框架。它只关注视图层,也就是 MVC 模式中的 V(视图 View)。React 的核心思想是组件化,将 UI 拆分成独立的、可复用的组件。

状态管理

React中最重要的概念之一是状态(state),它用于存储组件中的数据。以下是一个简单的状态示例:

import React, { useState } from 'react';

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

  function handleIncrement() {
    setCount(count + 1);
  }

  function handleDecrement() {
    setCount(count - 1);
  }

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

export default Counter;

条件渲染

React允许我们根据条件显示或隐藏组件。以下是一个简单的条件渲染示例:

import React from 'react';

function Greeting(props) {
  const isLoggedIn = props.isLoggedIn;

  if (isLoggedIn) {
    return <h1>Welcome back!</h1>;
  }
  else {
    return <h1>Please sign up.</h1>;
  }
}

export default Greeting;

列表和Keys

在React中,可以通过使用.map()方法进行遍历,从而将数组渲染为一组组件。以下是一个简单的渲染列表示例:

import React from 'react';

function List(props) {
  const items = props.items;

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

export default List;

表单

在React中,可以通过使用表单来收集用户输入。以下是一个简单的表单示例:

import React, { useState } from 'react';

function Form() {
  const [name, setName] = useState('');

  function handleSubmit(event) {
    event.preventDefault();
    alert(`Hello, ${name}!`);
  }

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Name:
        <input type="text" value={name} onChange={event => setName(event.target.value)} />
      </label>
      <button type="submit">Submit</button>
    </form>
  );
}

export default Form;

React 生命周期

React 生命周期是控制 React 组件在不同状态下行为的函数集合。以下是 React 组件生命周期的各个阶段:

  • Mounting - 组件被创建并放置在 DOM 中
  • Updating - 组件更新并重新渲染
  • Unmounting - 组件被从 DOM 中移除

Mounting 阶段

Mounting 阶段发生在组件第一次被渲染到 DOM 中,它包含以下生命周期函数:

class Example extends React.Component {
  constructor(props) {
    super(props);
    console.log("constructor");
  }

  componentDidMount() {
    console.log("componentDidMount");
  }

  render() {
    return <h1>Hello, {this.props.name}!</h1>;
  }
}
  • constructor - 构造函数,通常用于初始化组件状态。
  • componentDidMount - 在组件被渲染到 DOM 后,调用 componentDidMount 函数,可以进行一些操作,如调用API,设置计时器等。

Updating 阶段

Updating 阶段发生在组件更新后,它包含以下生命周期函数:

class Example extends React.Component {
  shouldComponentUpdate(nextProps, nextState) {
    console.log("shouldComponentUpdate");
    return true;
  }

  componentDidUpdate() {
    console.log("componentDidUpdate");
  }

  render() {
    return <h1>Hello, {this.props.name}!</h1>;
  }
}
  • shouldComponentUpdate - 告诉 React 是否有必要重新渲染组件。默认情况下不会引起渲染,只有在下面的情况下才会引起更新:

    • 组件的 props 或 state 发生变化
    • 父组件重新渲染
  • componentDidUpdate - 在组件更新完成后,调用 componentDidUpdate 函数来更新 DOM,从而更新组件。

Unmounting 阶段

Unmounting 阶段发生在组件从 DOM 中被卸载之前,它只包含一个生命周期函数:

class Example extends React.Component {
  componentWillUnmount() {
    console.log("componentWillUnmount");
  }

  render() {
    return <h1>Hello, {this.props.name}!</h1>;
  }
}
  • componentWillUnmount - 在组件从 DOM 中卸载之前调用,可以进行一些清理工作,如取消计时器、撤销 API 请求等。

高阶组件

高阶组件(Higher-Order Components,HOC)是一个用于重用组件逻辑的强大技术。以下是一个简单的示例:

import React from 'react';

function withTimestamp(WrappedComponent) {
  return class extends React.Component {
    render() {
      return <WrappedComponent timestamp={new Date()} {...this.props} />;
    }
  };
}

function Greeting(props) {
  return (
    <div>
      <h1>Hello, {props.name}!</h1>
      <p>Current time: {props.timestamp.toString()}</p>
    </div>
  );
}

const EnhancedGreeting = withTimestamp(Greeting);

export default EnhancedGreeting;

withLoading 高阶组件

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

function withLoading(WrappedComponent) {
  return class extends React.Component {
    state = {
      loading: true,
      data: null,
      error: null
    };

    async componentDidMount() {
      try {
        const response = await fetch(this.props.url);
        const data = await response.json();
        this.setState({ data, loading: false });
      } catch (error) {
        this.setState({ error, loading: false });
      }
    }

    render() {
      if (this.state.loading) {
        return <p>Loading...</p>;
      } else if (this.state.error) {
        return <p>Sorry, an error occurred: {this.state.error.message}</p>;
      } else {
        return <WrappedComponent {...this.props} data={this.state.data} />;
      }
    }
  };
}

function UserList({ data }) {
  return (
    <ul>
      {data.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

const UserListWithLoading = withLoading(UserList);

function App() {
  return (
    <div>
      <h1>List of users</h1>
      <UserListWithLoading
        url="https://jsonplaceholder.typicode.com/users"
      />
    </div>
  );
}

withRouter 高阶组件

javascript复制代码
import React from 'react';
import { withRouter } from 'react-router-dom';

function NavBar({ history }) {
  function handleClick() {
    history.push('/about');
  }

  return (
    <nav>
      <h1>My App</h1>
      <button onClick={handleClick}>Go to About page</button>
    </nav>
  );
}

export default withRouter(NavBar);

这是使用了React Router中的withRouter高阶组件,它会将路由相关的属性(如history对象)传递给被包裹的组件(NavBar)。在上面的示例中,点击按钮时,我们调用history.push方法将页面导航到/about路径。

上下文

React上下文(Context)是一种跨组件层次传递数据的方法。以下是一个使用React上下文的示例:

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

const MyContext = createContext('default');

function Child() {
  const value = useContext(MyContext);
  return <p>Value: {value}</p>;
}

function Parent() {
  return (
    <MyContext.Provider value="hello">
      <Child />
    </MyContext.Provider>
  );
}

export default Parent;

以下是 React 组件的一个示例代码,它展示了如何使用 class 组件、props 和 state 来创建一个简单的计数器:

import React from 'react';

class Counter extends React.Component {
  constructor(props) {
    super(props);

    this.state = { count: 0 };

    this.handleIncrement = this.handleIncrement.bind(this);
    this.handleDecrement = this.handleDecrement.bind(this);
  }

  handleIncrement() {
    this.setState({ count: this.state.count + 1 });
  }

  handleDecrement() {
    this.setState({ count: this.state.count - 1 });
  }

  render() {
    return (
      <div>
        <h1>Count: {this.state.count}</h1>
        <button onClick={this.handleIncrement}>+</button>
        <button onClick={this.handleDecrement}>-</button>
      </div>
    );
  }
}

export default Counter;

在上述代码中,我们首先定义了一个名为 Counter 的类组件,并在其中通过构造函数创建了一个名为 count 的 state 变量,初始值为 0。另外,为了处理用户输入的事件,在组件中也定义了两个方法:handleIncrement()handleDecrement()

Props 属性传递

在组件的 render 方法中,我们使用 JSX 模板语言编写了包含一个标题和两个按钮的 UI。其中,按钮分别调用了 handleIncrement()handleDecrement() 方法来增加或减少计数器的值,并使用大括号来绑定当前 state 中的 count 变量到视图中。 以下是关于 React 组件传值和 Store 全局数据存储的示例代码:

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

function App() {
  return (
    <div>
      <h1>Welcome to my app</h1>
      <Button text="Click me!" />
    </div>
  );
}

export default App;

在上述代码中,我们创建了一个名为 App 的组件,并在其中使用 Button 组件。通过将 text 属性赋值为 "Click me!",我们将统一这个值传递给了 Button 组件进行显示。

父子组件传参

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

function ParentComponent() {
  const [message, setMessage] = useState("");

  function handleChildButtonClick(msg) {
    setMessage(msg);
  }

  return (
    <div>
      <ChildComponent onButtonClick={handleChildButtonClick} />
      <p>{message}</p>
    </div>
  );
}

export default ParentComponent;

在上述代码中,我们创建了一个名为 ParentComponent 的组件,并使用 useState Hook 创建了名为 message 的 state 变量。接着,我们定义了一个名为 handleChildButtonClick() 的方法,用来接收子组件传递过来的参数并更新 message 变量的值。

在 render 方法中,我们对 <ChildComponent> 组件传递了一个名为 onButtonClick 的 props 属性,并将 handleChildButtonClick() 方法赋值给了它。这样,在子组件中调用 onButtonClick() 方法时,就能够触发父组件的方法并传递参数了。

Router路由

React Router DOM提供了三个不同的组件来帮助应用完成路由功能:

  1. BrowserRouter:使用HTML5 history API实现路由跳转,允许你使用类似于传统Web应用的URL,并支持前端路由取代传统后端路由的方式。
  2. Route:用于在应用中声明路由规则,可以指定一个路径和一个组件,当URL匹配到该路径时,Router将渲染指定组件。
  3. Link:用于在应用中跳转路由,允许用户与应用透明地交互,并进行页面跳转,它会产生一个类似于<a>标签的效果,但是不会刷新整个页面,而是仅更新匹配到的组件部分。

React Router还提供了其他一些有用的组件,例如Switch、Redirect和NavLink等,用于路由切换、重定向和激活导航等操作。

当使用React Router时,通常需要在应用的顶级组件中包含BrowserRouter,并使用Route来声明路由规则,例如:

import { BrowserRouter, Route, Switch } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About'

function App() {
  return (
    <BrowserRouter>
      <Switch>
        <Route exact path="/" component={Home} />
        <Route exact path="/about" component={About} />
      </Switch>
    </BrowserRouter>
  );
}

export default App;

**

在上述代码中,我们先导入了BrowserRouter、Route和Switch等组件。其中BrowserRouter用于包裹整个应用程序,Route用于声明路由规则,Switch用于渲染与当前URL匹配的第一个Route。

我们通过exact属性来确保路由精确匹配,path属性指定了该路由对应的路径,component属性指定了该路径匹配时要渲染的组件。

另外,我们可以在组件中使用Link组件用于跳转路由,例如:

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

function Header() {
  return (
    <nav>
      <Link to="/">Home</Link>
      <Link to="/about">About</Link>
    </nav>
  );
}

export default Header;

**

to属性指定了跳转的路径,当该链接被点击时,React Router会根据指定的路径匹配对应的Route进行渲染。

Redux Store 全局数据存储

以下是一个完整的 React + Redux 应用程序示例代码,展示了如何将全局数据存储和管理在 Redux Store 中,并在组件中进行调用和更新:

创建 Actions

// types.js
export const INCREMENT = 'INCREMENT';
export const DECREMENT = 'DECREMENT';

// actions.js
import { INCREMENT, DECREMENT } from './types';

export function increment() {
  return { type: INCREMENT };
}

export function decrement() {
  return { type: DECREMENT };
}

在上述代码中,我们定义了两个名为 INCREMENTDECREMENT 的 Action 类型常量,并在 actions.js 文件中通过 increment()decrement() 方法来分别返回这两种类型的 Action。当组件需要进行状态更新时,只需要调用相应方法并将其返回值传递给 Redux Store 即可。

创建 Reducer

// reducer.js
import { INCREMENT, DECREMENT } from './types';

const initialState = {
  count: 0,
};

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

export default reducer;

在上述代码中,我们定义了一个名为 reducer 的纯函数,用于处理对 Store 的更新操作。在初始化状态时,我们使用了一个名为 initialState 的对象来进行默认赋值。当接收到 INCREMENTDECREMENT ActionType 时,我们分别对计数器的值进行加一和减一的操作,并通过展开 state 对象来返回更新后的新状态。

创建 Store

// store.js
import { createStore } from 'redux';
import reducer from './reducer';

const store = createStore(reducer);

export default store;

在上述代码中,我们调用了 Redux 提供的 createStore() 方法,将 reducer 纯函数传递给它以创建一个名为 store 的 Store 实例。在 Store 实例被创建完成之后,我们导出这个 Store 以便于在组件中进行调用和使用。

在组件中调用并操作 Store 数据

import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from './actions';

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

  function handleIncrement() {
    dispatch(increment());
  }

  function handleDecrement() {
    dispatch(decrement());
  }

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={handleIncrement}>+</button>
      <button onClick={handleDecrement}>-</button>
    </div>
  );
}

export default Counter;

在上述代码中,我们使用了 useSelector() Hook 来从 Store 中获取当前计数值,并使用 useDispatch() Hook 来将 Action Creators 转发到 Store 中实现状态更新。当用户点击 "+" 或 "-" 按钮时,组件会触发 handleIncrement()handleDecrement() 方法,并通过调用相应的 Action Creators 来更新 Store 中的计数器值。

通过以上代码示例,我们可以看到如何使用 Redux 进行全局数据管理,在 React 应用程序中实现组件之间的状态共享和传递。

Hook函数

useState

javascript复制代码
import React, { useState } from 'react';

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

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}

useEffect()

useEffect是React Hook中常用的函数之一,用于在组件渲染完成后进行一些副作用操作,例如:获取数据、订阅事件、更新DOM等。

useEffect在何时执行,取决于其第二个参数的情况。useEffect的第二个参数是一个数组,用于指定该副作用操作执行的条件。具体来说,useEffect的执行情况有以下几种:

当第二个参数为空数组时,只会在组件渲染完毕后执行一次,相当于类组件中的componentDidMount。
useEffect(() => {
  // 副作用操作
}, []);

**

当第二个参数不为空数组时,仅在数组中的元素发生变化时执行,相当于类组件中的componentDidUpdate。
const [count, setCount] = useState(0);

useEffect(() => {
  // 副作用操作
  console.log(`count的值为${count}`);
}, [count]);

**

上述代码中,只有count的值发生变化时,useEffect才会被执行。

当第二个参数为undefined时,每次组件渲染完成后均会执行副作用操作,相当于类组件中的componentDidMount和componentDidUpdate的组合。
useEffect(() => {
  // 副作用操作
});

**

需要注意的是,当useEffect返回一个函数时,该函数将在组件销毁时执行,相当于类组件中的componentWillUnmount。

useEffect(() => {
  // 副作用操作

  return () => {
    // 清理操作
  };
});

**

总而言之,useEffect可以用于在组件渲染完成后进行一些副作用操作,并根据第二个参数来控制它的执行情况。

useEffect() Hook 用于在组件渲染之后执行副作用操作(如数据请求、状态变更等)。它接收两个参数:一个回调函数和依赖项数组。当依赖项数组中的任何一个元素发生变化时, useEffect() 都会再次执行回调函数。另外,如果依赖项数组为空或未提供,则回调函数将在每次渲染组件时执行。

举例来说,以下代码演示了如何在 useEffect() 中进行数据请求:

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

function MyComponent() {
  const [data, setData] = useState([]);
  
  useEffect(() => {
    fetch('https://my.api.com/data')
      .then(response => response.json())
      .then(data => setData(data));    
  }, []);

  return (
    <ul>
      {data.map(item => <li key={item.id}>{item.name}</li>)}
    </ul>
  );
}

export default MyComponent;

在上述代码中,我们使用 useState() Hook 创建了一个名为 data 的 state 变量,并在 useEffect() Hook 中通过 fetch() 方法进行了数据请求。由于要确保仅在组件第一次加载时执行一次数据请求并加载数据,我们将空数组作为第二个参数传递给 useEffect(),从而将其作为依赖项放入。最终,在成功获取和解析数据之后,我们将其存储在 data 变量中,并通过使用 map() 方法对其进行渲染。

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

export default function App() {
  const [state, setState] = useState(0);
  
  const timer = useEffect(() => {
      setInterval(()=>{
          setState((prev)=>prev+1)
      },1000)
      // 清除定时器
      return () => clearInterval()
  },[])

  return (
    <div>
        <h2>{state}</h2>
    </div>
  );
}

在useEffect中进行清除定时器的操作。

useContext()

useContext() Hook 用于在组件间共享上下文数据,避免了通过 props 层层传递数据的麻烦。它接收一个 Context 对象作为参数,并返回与上下文相关的值。需注意,必须将该 Context Provider 装置在当前组件的组件树中。

举例来说,以下代码创建了一个名为 ThemeContext 的 Context 对象,定义了主题颜色,并在不同的组件中使用 useContext() 来获取该上下文数据:

import React, { useContext } from 'react';

const ThemeContext = React.createContext('light');

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

  return <button style={{ background: theme.background, color: theme.color }}>I am styled by theme context!</button>
}

function MyApp() {
  return (
    <ThemeContext.Provider value={{ background: 'black', color: 'white' }}>
      <ThemedButton />
    </ThemeContext.Provider>
  );
}

export default MyApp;

在以上代码中,我们使用 createContext() 方法创建了一个名为 ThemeContext 的上下文对象,并将默认值设为 "light"。在 ThemedButton 组件中,我们通过调用 useContext() Hook 来获取 ThemeContext 上下文值,并将其应用到按钮样式。最后,在 MyApp 根组件中,我们将主题数据作为 Prop 值传递给了 ThemeContext.Provider,并将 ThemedButton 组件装置在该 Provider 下面。

通过上述代码示例,我们可以看到如何使用 React Hook 来简化组件间状态共享、数据请求等任务。除了 useEffect()useContext() 之外,React Hook 还提供了许多实用的 Hook,如 useState()useMemo()useReducer() 等。建议在项目中大量使用它们,以便优化代码和提高开发效率。