电子版: React 模式
          
            一份关于 react 的学习资料
          
          
        
        函数组件
函数组件 是最简单的一种声明可复用组件的方法
| 特色 | 代码 | 
|---|
| 基本Greeting | function Greeting() {
  return <div>Hi there!</div>;
}
 | 
| 从第一个形参中获取属性集 (props) | function Greeting(props) {
  return <div>Hi {props.name}!</div>;
}
 | 
| 按自己的需要可以在函数组件中定义任意变量 | function Greeting(props) {
  let style = {
    fontWeight: "bold",
    color: context.color
  };
  return <div style={style}>Hi {props.name}!</div>;
}
 | 
| 使用 defaultProps 为任意必有属性设置默认值 | function Greeting(props) {
  return <div>Hi {props.name}!</div>;
}
Greeting.defaultProps = {
  name: "Guest"
};
 | 
属性解构 (Destructuring props)
| 特性 | 代码 | 
|---|
| 基本语法 | // 好比字面量赋值的反转形式。
let person = { name: "chantastic" };
let { name } = person;
// 同样适用于数组。
let things = ["one", "two"];
let [first, second] = things;
 | 
| 解构赋值被用在很多 函数组件 | function Greeting(props) {
  return <div>Hi {props.name}!</div>;
}
function Greeting({ name }) {
  return <div>Hi {name}!</div>;
}
 | 
| 剩余参数(Rest) | function Greeting({ name, ...restProps }) {
  return <div>Hi {name}!</div>;
}
 | 
JSX 中的属性展开 (JSX spread attributes)
它是一种语法,专门用来把对象上的属性转换成 JSX 中的属性
| 特性 | 代码 | 
|---|
| 我们可以 扩散 restProps 对象的所有属性到 div 元素上 | function Greeting({ name, ...restProps }) {
  return <div {...restProps}>Hi {name}!</div>;
}
 | 
| 这让 Gretting 组件变得非常灵活,我们可以通过传给 Gretting 组件 DOM 属性并确定这些属性一定会被传到 div 上 | <Greeting name="Fancy pants" className="fancy-greeting" id="user-greeting" />
 | 
合并解构属性和其它值 
组件就是一种抽象,好的抽象是可以扩展的。
| 特性 | 代码 | 
|---|
| 比如说下面这个组件使用 class 属性来给按钮添加样式 | function MyButton(props) {
  return <button className="btn" {...props} />;
}
<MyButton className="delete-btn">Delete...</MyButton>
 | 
| JSX 中的属性展开 对先后顺序是敏感的 | // 扩散属性中的 className 会覆盖组件上的 className。
// 我们可以改变它两的顺序,但是目前来说 className 只有 btn。
function MyButton(props) {
  return <button {...props} className="btn" />;
}
 | 
| 扩展上面的情况 | // 实际中,我们可以用 classnames 这个库来完成下面的逻辑
function MyButton({ className = "", ...props }) {
  let classNames = ["btn", className].join(" ");
  return <button className={classNames} {...props} />;
}
 | 
条件渲染 (Conditional rendering)
- 不可以在一个组件声明中使用 if/else 语句 You can't use if/else statements inside a component declarations.
- 所以可以使用 条件(三元)运算符 和 短路计算。
| 特性 | 代码 | 
|---|
| 如果(if) | {
  condition && <span>Rendered when `truthy`</span>;
}
 | 
| 除非(else),其实就是 unless | {
  condition || <span>Rendered when `falsy`</span>;
}
 | 
| 如果-否则 | {
  condition ? (
    <span>Rendered when `truthy`</span>
  ) : (
    <span>Rendered when `falsy`</span>
  );
}
 | 
子元素类型 (Children types)
| 特性 | 代码 | 
|---|
| 字符串 String | <div>Hello World!</div>
 | 
| 数组 Array | <div>{["Hello ", <span>World</span>, "!"]}</div>
 | 
| 我们使用 map()方法创建一个新的 React 元素数组 | // 方式1
<ul>
  {["first", "second"].map(item => (
    <li>{item}</li>
  ))}
</ul>
// 等价形式
<ul>{[<li>first</li>, <li>second</li>]}</ul>
 | 
| 联合解构、JSX 属性扩散以及其它组件一起使用,简洁无比 | <ul>
  {arrayOfMessageObjects.map(({ id, ...message }) => (
    <Message key={id} {...message} />
  ))}
</ul>
 | 
| 函数做为子元素 (Function as children) | React 组件不支持函数类型的子元素。
然而 渲染属性 是一种可以创建组件并以函数作为子元素的模式。
 | 
| 渲染属性 (Render prop) | // 字义组件
const Width = ({ children }) => children(500);
// 使用组件
<Width>{width => <div>window is {width}</div>}</Width>
// 渲染策略
<Width>
  {width => (width > 600 ? <div>min-width requirement met!</div> : null)}
</Width>
 | 
代理组件 (Proxy component)
(我并不确定这个名字的准确叫法 译:代理、中介、装饰?)
| 特性 | 代码 | 
|---|
| 将一些属性进行封装 | 按钮在 web 应用中随处可见。并且所有的按钮都需要一个 type="button" 的属性。
<button type="button">
重复的写这些属性很容易出错。我们可以写一个高层组件来代理 props 到底层组件。
const Button = props =>
  <button type="button" {...props}>
 | 
| 样式组件 (Style component) | import classnames from "classnames";
const PrimaryBtn = props => <Btn {...props} primary />;
const Btn = ({ className, primary, ...props }) => (
  <button
    type="button"
    className={classnames("btn", primary && "btn-primary", className)}
    {...props}
  />
);
 | 
| 样式组件-几种形式 | <PrimaryBtn />
<Btn primary />
<button type="button" className="btn btn-primary" />
 | 
布局组件 (Layout component)
布局组件表现为一些静态 DOM 元素的形式。它们一般并不需要经常更新。
| 特性 | 代码 | 
|---|
| 就像下面的这个组件一样,两边各自渲染了一个 children。 | <HorizontalSplit
  leftSide={<SomeSmartComponent />}
  rightSide={<AnotherSmartComponent />}
/>
 | 
| HorizontalSplit 组件是两个子组件的父元素,我们可以告诉组件永远都不要更新 | class HorizontalSplit extends React.Component {
  shouldComponentUpdate() {
    return false;
  }
  render() {
    <FlexContainer>
      <div>{this.props.leftSide}</div>
      <div>{this.props.rightSide}</div>
    </FlexContainer>
  }
}
 | 
容器组件 (Container component)
「容器用来获取数据然后渲染到子组件上,仅仅如此。」
| 特性 | 代码 | 
|---|
| 静态数据 | const CommentList = ({ comments }) => (
  <ul>
    {comments.map(comment => (
      <li>
        {comment.body}-{comment.author}
      </li>
    ))}
  </ul>
);
 | 
| 自带请求的数据组件 | class CommentListContainer extends React.Component {
  constructor() {
    super()
    this.state = { comments: [] }
  }
  componentDidMount() {
    $.ajax({
      url: "/my-comments.json",
      dataType: 'json',
      success: comments =>
        this.setState({comments: comments});
    })
  }
  render() {
    return <CommentList comments={this.state.comments} />
  }
}
 | 
高阶组件 (Higher-order component)
| 特性 | 代码 | 
|---|
| 其实就一个函数,能返回一个组件的函数 | const Connect = ComposedComponent =>
  class extends React.Component {
    constructor() {
      super();
      this.state = { name: "" };
    }
    componentDidMount() {
      // this would fetch or connect to a store
      this.setState({ name: "Michael" });
    }
    render() {
      return <ComposedComponent {...this.props} name={this.state.name} />;
    }
  };
 | 
状态提升 (State hoisting)
- 函数组件 没有状态 (就像名字暗示的一样)。
- 事件是状态的变化。
- 它们的数据需要传递给状态化的父 容器组件
- 这就是所谓的「状态提升」
这个状态 被提升 到了容器中,通过添加回调函数,回调中可以更新本地状态。这就设置了一个很清晰边界,并且使功能组件的可重用性最大化。
这个模式并不限于函数组件。因为函数组件没有生命周期事件,你也可以在类组件中使用这种模式。
受控输入 是一种与状态提升同时使用时很重要的模式
| 特性 | 代码 | 
|---|
| 它是通过将回调从容器组件传递给子组件来完成的 | class NameContainer extends React.Component {
  constructor() {
    super();
    this.state = { name: "" };
  }
  render() {
    return <Name onChange={newName => this.setState({ name: newName })} />;
  }
}
const Name = ({ onChange }) => (
  <input onChange={e => onChange(e.target.value)} />
);
 | 
受控输入 (Controlled input)
- 受控组件Controlled Components: 在受控组件中,组件的状态(例如input的值)由React的state管理。每当用户与表单元素交互时,onChange等事件处理函数会更新组件的状态,从而使UI与状态保持同步。这样的方式让React完全掌控组件的状态,并且使得状态的变化可以追踪和操作。
- 非受控组件Uncontrolled Components: 非受控组件中,表单元素的值不受React的state控制,而是由DOM自身维护。通常通过使用ref来获取表单元素的值,这种方式比较适合一些简单的交互,但在复杂情况下可能会导致代码难以维护和调试。
| 特性 | 代码 | 
|---|
| 一个不受控的(通常)输入 | <input type="text" />
<input type="text" defaultValue="1231" />
 | 
| 受控的情况 | class ControlledNameInput extends React.Component {
  constructor() {
    super();
    this.state = { name: "" };
  }
  render() {
    return (
      <input
        value={this.state.name}
        onChange={(e) => this.setState({ name: e.target.value })}
      />
    );
  }
}
 | 
| 受控组件(Controlled Components) | import React, { Component } from 'react';
class ControlledComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      inputValue: ''
    };
  }
  handleInputChange = (event) => {
    this.setState({ inputValue: event.target.value });
  };
  render() {
    return (
      <div>
        <input
          type="text"
          value={this.state.inputValue}
          onChange={this.handleInputChange}
        />
        <p>Input Value: {this.state.inputValue}</p>
      </div>
    );
  }
}
export default ControlledComponent;
 | 
| 非受控组件(Uncontrolled Components) | import React, { Component } from 'react';
class UncontrolledComponent extends Component {
  constructor(props) {
    super(props);
    this.inputRef = React.createRef();
  }
  handleButtonClick = () => {
    console.log('Input Value:', this.inputRef.current.value);
  };
  render() {
    return (
      <div>
        <input type="text" ref={this.inputRef} />
        <button onClick={this.handleButtonClick}>Get Input Value</button>
      </div>
    );
  }
}
export default UncontrolledComponent;
 | 
| 非受控组件的 value 初始化 | import React, { Component } from 'react';
class UncontrolledComponentWithInitialValue extends Component {
  constructor(props) {
    super(props);
    this.inputRef = React.createRef();
  }
  handleButtonClick = () => {
    console.log('Input Value:', this.inputRef.current.value);
  };
  render() {
    return (
      <div>
        <input type="text" defaultValue="Initial Value" ref={this.inputRef} />
        <button onClick={this.handleButtonClick}>Get Input Value</button>
      </div>
    );
  }
}
export default UncontrolledComponentWithInitialValue;
 | 
关于受控与非受控组件
总结来说,受控组件提供了更强大的状态管理和交互控制,适用于需要更精细控制的情况。
而非受控组件则可以在一些简单场景下减少代码量,但可能会失去React状态管理的优势。选择使用哪种方式取决于项目的需求和开发者的偏好。
参考