React 设计模式 0x1:组件

2023-05-17 21:06:05 浏览数 (1)

学习如何轻松构建可伸缩的 React 应用程序:编写组件

# 命名规范

编程中常见的命名方式有:

驼峰式命名法(Camel Case),也叫小驼峰式命名法(Lower Camel Case)

代码语言:javascript复制
const camelCase = "camelCase";

帕斯卡命名法(Pascal Case),也叫大驼峰式命名法(Upper Camel Case)

代码语言:javascript复制
const PascalCase = "PascalCase";

在 React 中,组件的命名方式是大驼峰式命名法,即组件的名称必须以大写字母开头。

代码语言:javascript复制
import React from "react";

const MyComponent = () => {
  return <div>My Component</div>;
};

export default MyComponent;

# 函数式组件

函数组件是普通的 JavaScript 函数,它接收 props 作为输入并返回一个 React 组件。

可以是一个箭头函数:

代码语言:javascript复制
import React from "react";

const MyComponent = (props) => {
  return <div>My Component</div>;
};

export default MyComponent;

或普通函数:

代码语言:javascript复制
import React from "react";

function MyComponent(props) {
  return <div>My Component</div>;
}

export default MyComponent;

函数组件具有一些常见的 hook 。React hooks 使得大多数开发人员能够构建可伸缩的 React 应用程序。

# useState

useState 是 React 中最常用的 hook 之一,它用于在函数式组件中存储状态值(对象、字符串、布尔值等),这些值在组件的生命周期中进行变更。useState 接受一个初始值,如果是字符串则可以为空字符串,这个值可以在组件的生命周期中进行更新。

代码语言:javascript复制
import React, { useState } from "react";

const MyComponent = () => {
  const [name, setName] = useState("");

  return (
    <div>
      <h1>My Component</h1>
      <input type="text" value={name} onChange={(e) => setName(e.target.value)} />
    </div>
  );
};

export default MyComponent;

# useRef

useRef 方法也是大多数函数组件中常用的 React hooks 之一。useRef 方法常用于指向 DOM 中的一个元素,可用于创建不受控制的元素。

代码语言:javascript复制
import React, { useRef } from "react";

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

  const browseFile = () => {
    if (inputRef.current) {
      inputRef.current.click();
    }
  };

  return (
    <div>
      <input type="file" ref={inputRef} />
      <button onClick={browseFile}>Browse File</button>
    </div>
  );
};

export default MyComponent;

# useEffect

useEffect 方法也是大多数功能组件中常用的 React hook 。useEffect 方法是一种异步钩子,让我们可以在组件上执行异步任务,这些异步任务包括调用 API 并通过 useState 保存数据。

useEffect 接受两个参数,分别是:

  • 带有可选的返回语句的函数
    • 可选的返回语句是一个函数,它在组件卸载时执行,用于进行清理工作,如定时器、事件监听器等
  • 可选的依赖项数组
    • 当不传入依赖项数组时,useEffect 会在每次渲染时执行
    • 当传入依赖项数组时
      • 如果数组为空,则 useEffect 只会在组件挂载时执行
      • 如果数组不为空,则 useEffect 会在组件挂载时执行,以及当数组中的任何值发生变化时执行
代码语言:javascript复制
import React, { useState, useEffect } from "react";

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

  useEffect(() => {
    console.log("render component");
  });

  useEffect(() => {
    console.log("componentDidMount");
    return () => {
      console.log("componentWillUnmount");
    };
  }, []);

  useEffect(() => {
    console.log("componentDidUpdate");
    document.title = `You clicked ${count} times`;
  }, [count]);

  return (
    <div>
      <h1>My Component</h1>
      <input type="text" value={name} onChange={(e) => setName(e.target.value)} />
      <button onClick={() => setCount(count   1)}>Click Me</button>
    </div>
  );
};

export default MyComponent;

# 类组件

类组件是继承自 React.component 的子类组件,这个类组件接受 props 并渲染它们,它以一个 constructor 开始,这个 constructor 会被超类调用。

代码语言:javascript复制
import React from "react";

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

  render() {
    return <div>My Component</div>;
  }
}

export default MyComponent;

类组件有一些常见的生命周期方法:

  • componentDidMount
  • componentDidUpdate

# componentDidMount

该生命周期方法在 React 组件生命周期的挂载阶段被调用,这个方法可以帮助我们在向用户展示数据之前修改 React 组件的内容。

代码语言:javascript复制
import React from "react";

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      name: "",
    };
  }

  componentDidMount() {
    this.setState({ name: "Cell" });
  }

  render() {
    return <div>My name is {this.state.name}</div>;
  }
}

export default MyComponent;

# componentDidUpdate

该生命周期方法在组件更新后被调用:

代码语言:javascript复制
import React from "react";

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      name: "",
    };
  }

  componentDidMount() {
    this.setState({ name: "Cell" });
  }

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

  render() {
    return <div>My name is {this.state.name}</div>;
  }
}

export default MyComponent;

# 组件结构最佳实践

React 组件是构建小型到强大应用程序的方式。这些 React 组件需要以良好的方式进行结构化,以便于进行测试、扩展和易于发现错误。

以下是保持良好的 React 组件结构的最佳方法:

  • 避免使用大型组件
    • 大型组件通常很难阅读、理解和调试
    • 即使应用程序正常运行,当出现问题时,如何调试也将是个问题
    • 应该将大型组件分解为较小的组件,以便于阅读、测试和轻松识别错误
  • 给组件和变量合适的命名
    • 编写合理的变量名、方法名或组件名非常重要
    • 避免使用模糊不清的命名
  • 保持文件夹结构精确和易于理解
    • 文件和文件夹结构在实现良好的组件结构方面也非常重要
    • 为项目提供文件夹结构,以便于理解应该将哪些文件放入特定文件夹中
  • 将可重用的逻辑移至单独的类或函数中
    • 通常在编程中,始终记住 DRY 原则
    • 无论您觉得应用程序或组件将使用哪些可重用的逻辑,都将其移至函数或方法中,并在应用程序中调用
  • 尝试编写测试
    • 测试可以确保您的组件按预期工作,并在编写得当时减少应用程序中的错误数量

# 组件数据共享

在 React 中,一定会在在组件之间共享数据,具体实现方式取决于状态变化的复杂程度和应用程序的大小。

以下是一些实现方式:

  • Props
  • Context API
  • Redux
  • useReducer

# Props

Props 是在 React 中从一个组件传递数据到另一个组件的一种方式,props 是从父组件传递到子组件的对象。Propsproperties(属性)的缩写。

代码语言:javascript复制
import React from "react";

const MyComponent = (props) => {
  const { name, age } = props;

  return (
    <div>
      <p>My name is {name}</p>
      <p>My age is {age}</p>
    </div>
  );
};

const App = () => {
  return <MyComponent name="Cell" age={18} />;
};

export default App;

# Context API

Context API 也是一种从一个组件传递数据到另一个组件的方式。与 Props 的主要区别在于,Context API 不会在每个组件上从父组件传递到子组件。

Context API 有两个主要方法:

  • Provider
    • Provider 接受一个要传递给子组件的值
  • Consumer
    • Consumer 允许调用组件订阅 context 更新
代码语言:javascript复制
import React from "react";

const MyContext = React.createContext();

const MyComponent = () => {
  return (
    <MyContext.Consumer>
      {(data) => (
        <div>
          <p>My name is {data.name}</p>
          <p>My age is {data.age}</p>
        </div>
      )}
    </MyContext.Consumer>
  );
};

const MyComponent2 = () => {
  const data = React.useContext(MyContext);

  return (
    <div>
      <p>My name is {data.name}</p>
      <p>My age is {data.age}</p>
    </div>
  );
};

const App = () => {
  return (
    <MyContext.Provider value={{ name: "Cell", age: 18 }}>
      <MyComponent />
      <MyComponent2 />
    </MyContext.Provider>
  );
};

export default App;

# Redux

Redux 是一个开源的 JavaScript 库,它保持全局状态以使应用程序具有一致的行为。

Redux 库包括以下三个部分:

  • Store
    • 用于存储全局状态
    • 这一部分是不可变的,即它无法改变
  • Reducer
    • Reducer 是一个纯函数,它接受两个参数(初始状态和操作),并返回一个新的状态
  • Actions
    • Action 是一个 JavaScript 对象,告诉 Reducer 用户希望在 Store 中执行什么操作
    • Action 是用户的指令,用于在 Store 中要么更改状态,要么创建状态的副本
代码语言:javascript复制
import { createStore } from "redux";

const initialState = {
  name: "",
  age: 0,
};

const reducer = (state = initialState, action) => {
  switch (action.type) {
    case "SET_NAME":
      return {
        ...state,
        name: action.payload,
      };
    case "SET_AGE":
      return {
        ...state,
        age: action.payload,
      };
    default:
      return state;
  }
};

const store = createStore(reducer);

store.subscribe(() => {
  console.log(store.getState());
});

store.dispatch({ type: "SET_NAME", payload: "Cell" });
// { name: "Cell", age: 0 }
store.dispatch({ type: "SET_AGE", payload: 18 });
// { name: "Cell", age: 18 }

# useReducer

useReducer 方法也是在组件之间共享数据的一种方式。当您需要进行复杂状态更改时,可以使用 useReducer 方法。

useReducer 方法接受参数为初始状态和操作,返回当前状态和 dispatch 方法。

代码语言:javascript复制
import { useReducer } from "react";

const initialState = {
  name: "",
  age: 0,
};

const reducer = (state, action) => {
  switch (action.type) {
    case "SET_NAME":
      return {
        ...state,
        name: action.payload,
      };
    case "SET_AGE":
      return {
        ...state,
        age: action.payload,
      };
    default:
      return state;
  }
};

const App = () => {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      <p>My name is {state.name}</p>
      <p>My age is {state.age}</p>
      <button onClick={() => dispatch({ type: "SET_NAME", payload: "Cell" })}>Set Name</button>
      <button onClick={() => dispatch({ type: "SET_AGE", payload: 18 })}>Set Age</button>
    </div>
  );
};

export default App;

# 受控组件 vs 非受控组件

受控组件数据是由 React 组件管理的,而非受控组件数据是由 浏览器或 DOM 处理。受控组件通常由用户输入或事件处理。

代码语言:javascript复制
import React, { useState, useRef } from "react";

const controlledComponent = () => {
  const [name, setName] = useState("");
  const [age, setAge] = useState(0);

  return (
    <div>
      <input type="text" value={name} onChange={(e) => setName(e.target.value)} />
      <input type="number" value={age} onChange={(e) => setAge(e.target.value)} />
    </div>
  );
};

const uncontrolledComponent = () => {
  const nameRef = useRef();
  const ageRef = useRef();

  const handleSubmit = () => {
    console.log(nameRef.current.value);
    console.log(ageRef.current.value);
  };

  return (
    <div>
      <input type="text" ref={nameRef} />
      <input type="number" ref={ageRef} />
      <button onClick={handleSubmit}>Submit</button>
    </div>
  );
};

const App = () => {
  return (
    <div>
      <controlledComponent />
      <uncontrolledComponent />
    </div>
  );
};

export default App;

0 人点赞