React从入门到精通教程04

React从入门到精通

系列教程:

  1. React从入门到精通教程01
  2. React从入门到精通教程02
  3. React从入门到精通教程03

表单

  1. 概述

在 React 里,HTML 表单元素的工作方式和其他的 DOM 元素有些不同,这是因为表单元素通常会保持一些内部的 state。比方说:

<form>
  <label>
    名字:
    <input type="text" name="name" />
  </label>
  <input type="submit" value="提交" />
</form>

这个表单中的input可以有自己维护的属性valueinput被点击后还会有默认的提交事件到服务器的默认事件,这些都不是我们所控制的

  1. 受控组件

在 HTML 中,表单元素(如<input><textarea><select>)之类的表单元素通常自己维护 state,并根据用户输入进行更新。而在 React 中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 setState()来更新。

我们可以把两者结合起来,使 React 的 state 成为“唯一数据源”。渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”。

input
class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ''};
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {    this.setState({value: event.target.value});  }
  handleSubmit(event) {
    alert('提交的名字: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          名字:
          <input type="text" value={this.state.value} onChange={this.handleChange} />        </label>
        <input type="submit" value="提交" />
      </form>
    );
  }

例如上面这个例子,我们把inputvalueonChangesubmit都绑定到了我们的NameForm组件上,这样表单里面的组件在变化时就不会使用自己的默认属性和默认事件了,而是使用我们定义好的属性和事件,这种方法可以方便我们空值组件内的dom元素

同理除了input之外,还有

textarea
<form onSubmit={this.handleSubmit}>
   <label>
      文章:
   		<textarea value={this.state.value} onChange={this.handleChange} />
         <!-- 绑定了value到state上,同时绑定了onChange事件到handkeChange上 -->
   </label>
   <input type="submit" value="提交" />
</form>
select

在 HTML 中,<select> 创建下拉列表标签。例如,如下 HTML 创建了水果相关的下拉列表:

<select>
  <option value="grapefruit">葡萄柚</option>
  <option value="lime">酸橙</option>
  <option selected value="coconut">椰子</option>
  <option value="mango">芒果</option>
</select>

请注意,由于 selected 属性的缘故,椰子选项默认被选中。React 并不会使用 selected 属性,而是在根 select 标签上使用 value 属性。这在受控组件中更便捷,因为您只需要在根标签中更新它。例如:

class FlavorForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: 'coconut'};
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {    this.setState({value: event.target.value});  }
  handleSubmit(event) {
    alert('你喜欢的风味是: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          选择你喜欢的风味:
          <select value={this.state.value} onChange={this.handleChange}>    /* 相当于使用时间代理的方式,不用为每个<option>都去定义事件,方便我们统一处理        */
            <option value="grapefruit">葡萄柚</option>
            <option value="lime">酸橙</option>
            <option value="coconut">椰子</option>
            <option value="mango">芒果</option>
          </select>
        </label>
        <input type="submit" value="提交" />
      </form>
    );
  }
}

总的来说,这使得 <input type="text">, <textarea><select> 之类的标签都非常相似—它们都接受一个 value 属性,你可以使用它来实现受控组件。

  1. 非受控组件

在 HTML 中,<input type="file"> 允许用户从存储设备中选择一个或多个文件,将其上传到服务器,或通过使用 JavaScript 的 File API 进行控制。

<input type="file" />

因为它的 value 只读,所以它是 React 中的一个非受控组件。也就是他的内置动作不能由我们自己控制。

  1. 处理多个输入

当需要处理多个 input 元素时,我们可以给每个元素添加 name 属性,并让处理函数根据 event.target.name 的值选择要执行的操作。

例如:

class Reservation extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isGoing: true,
      numberOfGuests: 2
    };

    this.handleInputChange = this.handleInputChange.bind(this);
  }

  handleInputChange(event) {
    const target = event.target;
    const value = target.name === 'isGoing' ? target.checked : target.value; // 通过input的name值来判断是哪个input触发了点击事件
    const name = target.name;
    this.setState({
      [name]: value    }); // 使用ES6的计算属性
  }

  render() {
    return (
      <form>
        <label>
          参与:
          <input
            name="isGoing"            
            type="checkbox"
            checked={this.state.isGoing}
            onChange={this.handleInputChange} />
        </label>
        <br />
        <label>
          来宾人数:
          <input
            name="numberOfGuests"            
            ype="number"
            value={this.state.numberOfGuests}
            onChange={this.handleInputChange} />
        </label>
      </form>
    );
  }
}
  1. 注意初始空值

我们使用受控组件的时候,会默认的用组件的state作为表单组件的props,但是我们要注意我们不要把state设置为null或者undefined

下面的代码演示了这一点。(输入最初被锁定,但在短时间延迟后变为可编辑。)

ReactDOM.render(<input value="hi" />, mountNode);

setTimeout(function() {
  ReactDOM.render(<input value={null} />, mountNode);
}, 1000);

状态提升

由于React的数据流向是自顶向下的,所以我们在定义状态的时候最好是使用单一数据源,比如有一个兄弟组件,他们需要使用同一个数据a,并且两个组件要保持这个数据的同步,这个时候如果我们分别在两个组件定义两个状态state,这样维护起来会非常麻烦,因为一个导致这个数据变化的源有两个,分别是这两个组件,这和时候我们采取的是进行状态提升,我们把这两个状态提升到最近的父组件的state中去,子组件通过props把数据传递进来,这样导致这个数据变化的原因就只有一种了,我们就很方便的来进行控制了。这就是数据提升,我们也可以去看看官方给的关于温度的这个例子。官方示例代码

在 React 应用中,任何可变数据应当只有一个相对应的唯一“数据源”。通常,state 都是首先添加到需要渲染数据的组件中去。然后,如果其他组件也需要这个 state,那么你可以将它提升至这些组件的最近共同父组件中。你应当依靠自上而下的数据流,而不是尝试在不同组件间同步 state。

虽然提升 state 方式比双向绑定方式需要编写更多的“样板”代码,但带来的好处是,排查和隔离 bug 所需的工作量将会变少。由于“存在”于组件中的任何 state,仅有组件自己能够修改它,因此 bug 的排查范围被大大缩减了。此外,你也可以使用自定义逻辑来拒绝或转换用户的输入。

如果某些数据可以由 props 或 state 推导得出,那么它就不应该存在于 state 中。比如我们定义了出生日期作为state,那么我们没必要把年龄也定义成state,而是可以定义成组件里面的一个变量。因为我们可以通过现在的日期和出生日期算出来这个人的年龄。

组合和继承

组合:

我们知道一个组件可以相当于是一个函数,我们传入props,返回的是将要展示在页面上的元素,我们之前用props传递的都是一些数据,那么我们能不能将组件或者其它元素作为props传入呢,答案是可以的,react为了提高代码灵活性肯定想到了这一点。

  1. props children

有些组件无法提前知晓它们子组件的具体内容。在 Sidebar(侧边栏)和 Dialog(对话框)等展现通用容器(box)的组件中特别容易遇到这种情况。

我们建议这些组件使用一个特殊的 children prop 来将他们的子组件传递到渲染结果中:

function FancyBorder(props) {
  return (
    <div className={'FancyBorder FancyBorder-' + props.color}>
      {props.children}
    </div>
  );
}

这使得别的组件可以通过 JSX 嵌套,将任意组件作为子组件传递给它们。

function WelcomeDialog() {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        Welcome
      </h1>
      <p className="Dialog-message">
        Thank you for visiting our spacecraft!
      </p>
    </FancyBorder>
  );
}

<FancyBorder> JSX 标签中的所有内容都会作为一个 children prop 传递给 FancyBorder 组件。因为 FancyBorder{props.children} 渲染在一个 <div> 中,被传递的这些子组件最终都会出现在输出结果中。

我们打印传进来的props

image-20210621213236896

和之前一样,props任然是一个对象,只不过组件的子节点都作为children属性的值传给了组件,我们也就可以动态的改变组件中的内容了.

  1. 仿slot

在其他的框架例如vue,我们可以使用slot来在组件中预留位置,以后传进来的组件放在对应nameslot中,react虽然没有slot,但是我们可以做到。

function SplitPane(props) {
  return (
    <div className="SplitPane">
      <div className="SplitPane-left">
        {props.left}      </div>
      <div className="SplitPane-right">
        {props.right}      </div>
    </div>
  );
}

function App() {
  return (
    <SplitPane
      left={
        <Contacts />      }
      right={
        <Chat />      } />
  );
}

<Contacts /><Chat /> 之类的 React 元素本质就是对象(object),所以你可以把它们当作 props,像其他数据一样传递。这种方法可能使你想起别的库中“槽”(slot)的概念,但在 React 中没有“槽”这一概念的限制,你可以将任何东西作为 props 进行传递。和上面的children传递不同,我们是直接作为组件的属性进行传递的,而不是组件的子节点

继承

除了在使用类组件的时候我们需要继承React.component。我们并没有发现需要使用继承来构建组件层次的情况。

Props 和组合为你提供了清晰而安全地定制组件外观和行为的灵活方式。注意:组件可以接受任意 props,包括基本数据类型,React 元素以及函数。

如果你想要在组件间复用非 UI 的功能,我们建议将其提取为一个单独的 JavaScript 模块,如函数、对象或者类。组件可以直接引入(import)而无需通过 extend 继承它们。

React哲学(最佳实践)

  1. 要确定好React组件的大小,你可以将组件当作一种函数或者是对象来考虑,根据单一功能原则来判定组件的范围。也就是说,一个组件原则上只能负责一个功能。如果它需要负责更多的功能,这时候就应该考虑将它拆分成更小的组件。

  2. 确定好组件之后我们要确定好组件的层次结构,最好是能在设计稿中体现出来,方便日后开发使用。

  3. 开发的时候最好将渲染 UI 和添加交互这两个过程分开。这是因为,编写一个应用的静态版本时,往往要编写大量代码,而不需要考虑太多交互细节;添加交互功能时则要考虑大量细节,而不需要编写太多代码。所以,将这两个过程分开进行更为合适。

  4. 你可以自上而下或者自下而上构建应用:自上而下意味着首先编写层级较高的组件(比如 FilterableProductTable),自下而上意味着从最基本的组件开始编写(比如 ProductRow)。当你的应用比较简单时,使用自上而下的方式更方便;对于较为大型的项目来说,自下而上地构建,并同时为低层组件编写测试是更加简单的方式。我们最顶层的组件接受我们的props就可以来测试我们的静态UI页面了。

  5. 确定我们的state数据有哪些(可以通过以下三个方面来判断是作为state还是普通变量):

    1. 该数据是否是由父组件通过 props 传递而来的?如果是,那它应该不是 state。
    2. 该数据是否随时间的推移而保持不变?如果是,那它应该也不是 state。
    3. 你能否根据其他 state 或 props 计算出该数据的值?如果是,那它也不是 state。
  6. 确认state的存放位置

    当我们确认好那些数据需要定义为state之后我们就应该考虑state存放在那个组件中,一般是需要渲染这个state的组件就需要存放,但是考虑之前的状态提升,有时候我们需要将state存放在兄弟节点的共同父节点中去。

  7. 添加反向的数据流

    以往我们的数据六都是单向的,但是如果我们将state定义在父组件中,那么我们就不能在子组件中去改变这个值,这时候我们就需要比较传统的方式去改变父组件中的state了。例如子组件<son />, 父组件<father />,父组件中有state name, 实现子组件在输入框内输入,父组件就可以动态的更新值。

    // 父组件
    class Father extends React.Component{
          constructor(props) {
            super(props)
            this.state = {
              name: 'father',
            }
            this.handleChangeOfFather = this.handleChangeOfFather.bind(this);
          }
          handleChangeOfFather(nameOfSon){ // 更改state中的值为子组件传过来的值
            this.setState({ name: nameOfSon })
          }
          render() {
            return (
              <div>
                <p>名字</p>
                <h1>{this.state.name}</h1>
                <Son  triggerChange = {this.handleChangeOfFather}/>    
    // 子组件触发triggerChange就会引起handleChangeOfFather触发,可以理解为input中的onChange,只不过这个是我们自己随意定义的
              </div>
            )
          }
        }
        // 子组件
        class Son extends React.Component{
          constructor(props) {
            super(props)
            this.handleChageOfSon = this.handleChageOfSon.bind(this);
          }
          handleChageOfSon(e){
            this.props.triggerChange(e.target.value); // 触发通过父组件传进来的triggerChange属性
          }
          render() {
            return (
              <input onChange = {this.handleChageOfSon} /> 
    //inpu的onChange调用handleChageOfSon 
            )
          }
        } 
    
        ReactDOM.render(
          <Father />,
          document.getElementById('root')
        );
    
  8. 以上一般就是我们自己编写一个React应用的基本步骤了,如果项目比较大,则远不止如此。

核心概念已讲完,接下来是React高级和其它相关知识的讲解

未完待续… …

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值