reactjs

getStarted

https://reactjs.org/tutorial/tutorial.html 基于javascript, 更纯,更灵活。

Create React App

  • npm install -g create-react-app, create-react-app myapp, cd myapp && npm start, npm run build输出到build文件夹

组件

React.Component

组件告诉React提交的内容。当数据有更改,react负责更新和提交。 组件带有属性参数props,通过render方法返回视图的描述,也就是react element。JSX句法可以简化写法。 JSX内可以写javascript表达式,可以赋给变量,或者方法间传递,是一个javascript对象。

class ShoppingList extends React.Component {
  render() {
    return (
      <div className="shopping-list">
        <h1>Shopping List for {this.props.name}</h1>
      </div>
    );
  }
}
//上面的return 等价
return React.createElement('div', {className: 'shopping-list'},
  React.createElement('h1', /* ... h1 children ... */)
);

代码赏析

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';

// class Square extends React.Component {
//     render() {
//       return (
//         <button className="square" onClick={() => this.props.onClick()}>
//            {this.props.value}
//         </button>
//       );
//     }
//   }
  function Square(props) {
    return (
      <button className="square" onClick={props.onClick}>
        {props.value}
      </button>
    );
  }

  class Board extends React.Component {
    renderSquareLine(line) {
      //Array(3)=[undefined*3], Array(3).keys()=ArrayIterator{}, [...Array(3).keys()]=[1,2,3]
      return [...Array(3).keys()].map((_, i) => {
        // return <Square value={this.props.squares[i + line * 3]}
        //   onClick={() => this.props.onClick(i + line * 3)} />
        return this.renderSquare(i + line * 3)
      });
    }
    renderSquare(i) {
      return <Square key={i} value={this.props.squares[i]}
      onClick={() => this.props.onClick(i)} />;
    }

    render() {
      return (
        <div>
          <div className="board-row">
            {this.renderSquareLine(0)}
          </div>
          <div className="board-row">
            {this.renderSquareLine(1)}
          </div>
          <div className="board-row">
            {this.renderSquare(6)}
            {this.renderSquare(7)}
            {this.renderSquare(8)}
          </div>
        </div>
      );
    }
  }

  class Game extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        history: [{
          squares: Array(9).fill(null),
        }],
        xIsNext: true,
        stepNumber: 0,
      };
    }
    jumpTo(step) {
      this.setState({
        stepNumber: step,
        xIsNext: (step % 2) === 0,
      });
    }
    handleClick(i) {
      const history = this.state.history.slice(0, this.state.stepNumber + 1);
      const current = history[history.length - 1];
      const squares = current.squares.slice();
      if (calculateWinner(squares) || squares[i]) {
        return;
      }
      squares[i] = this.state.xIsNext ? 'X' : 'O';
      this.setState({
        history: history.concat([{
          squares: squares,
        }]), xIsNext: !this.state.xIsNext,stepNumber: history.length,
      });
    }

    render() {
      const history = this.state.history;
      const current = history[this.state.stepNumber];
      const winner = calculateWinner(current.squares);
      let status = winner ? 'Winner: ' + winner: 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
      const moves = history.map((step, move) => {
        const desc = move ?
          'Go to move #' + move :
          'Go to game start';
        return (
          <li key={move}>
            <button onClick={() => this.jumpTo(move)}>{desc}</button>
          </li>
        );
      });
      return (
        <div className="game">
          <div className="game-board">
            <Board squares={current.squares}
            onClick={(i) => this.handleClick(i)} />
          </div>
          <div className="game-info">
            <div>{status}</div>
            <ol>{moves}</ol>
          </div>
        </div>
      );
    }
  }

  ReactDOM.render(
    <Game />,
    document.getElementById('root')
  );

function calculateWinner(squares) {
  const lines = [
    [0, 1, 2],
    [3, 4, 5],
    [6, 7, 8],
    [0, 3, 6],
    [1, 4, 7],
    [2, 5, 8],
    [0, 4, 8],
    [2, 4, 6],
  ];
  for (let i = 0; i < lines.length; i++) {
    const [a, b, c] = lines[i];
    if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
      return squares[a];
    }
  }
  return null;
}

[Thinking in react]https://reactjs.org/docs/thinking-in-react.html

UI打碎成层次化的组件

类似与设计师在photoshop中使用的层layer. 怎么判断那些应该成为一个组件?一个组件只做一件事。 常向用户展示json数据,也可以发现,组件与数据模型可以很好对应。

构建react的静态版本

静态版本不需要太多思考,大量敲键盘,而交互则相反。 静态版本不需要写state 小项目可以自顶向下,大项目相反。 静态版本完成后,会有许多提供数据模型的可重用组件。这些组件只有render方法。 最顶层的组建会把这些数据模型当作prop.

找到最小化的UI状态state表示

  • 如果是从parent那里通过props过来,那可能不是state
  • 不随着时间改变,那可能不是state
  • 可以通过组件的其他props和state获取,那可能不是state

找到state应该处在什么组件中

  • 找到所有基于状态渲染的组件
  • 找到一个公共的主组件
  • 公共组件或其之上的拥有state.
  • 如果找不到合适的拥有状态的组件,创建一个新的组件,并添加到公共组件之上。

添加反向数据流

class ProductCategoryRow extends React.Component {
  render() {
    const category = this.props.category;
    return (
      <tr>
        <th colSpan="2">
          {category}
        </th>
      </tr>
    );
  }
}

class ProductRow extends React.Component {
  render() {
    const product = this.props.product;
    const name = product.stocked ?
      product.name :
      <span style={{color: 'red'}}>
        {product.name}
      </span>;

    return (
      <tr>
        <td>{name}</td>
        <td>{product.price}</td>
      </tr>
    );
  }
}

class ProductTable extends React.Component {
  render() {
    const filterText = this.props.filterText;
    const inStockOnly = this.props.inStockOnly;

    const rows = [];
    let lastCategory = null;

    this.props.products.forEach((product) => {
      if (product.name.indexOf(filterText) === -1) {
        return;
      }
      if (inStockOnly && !product.stocked) {
        return;
      }
      if (product.category !== lastCategory) {
        rows.push(
          <ProductCategoryRow
            category={product.category}
            key={product.category} />
        );
      }
      rows.push(
        <ProductRow
          product={product}
          key={product.name}
        />
      );
      lastCategory = product.category;
    });

    return (
      <table>
        <thead>
          <tr>
            <th>Name</th>
            <th>Price</th>
          </tr>
        </thead>
        <tbody>{rows}</tbody>
      </table>
    );
  }
}

class SearchBar extends React.Component {
  constructor(props) {
    super(props);
    //bind to input element
    this.handleFilterTextChange = this.handleFilterTextChange.bind(this);
    this.handleInStockChange = this.handleInStockChange.bind(this);
  }

  handleFilterTextChange(e) {
    this.props.onFilterTextChange(e.target.value);
  }

  handleInStockChange(e) {
    this.props.onInStockChange(e.target.checked);
  }

  render() {
    return (
      <form>
        <input
          type="text"
          placeholder="Search..."
          value={this.props.filterText}
          onChange={this.handleFilterTextChange}
        />
        <p>
          <input
            type="checkbox"
            checked={this.props.inStockOnly}
            onChange={this.handleInStockChange}
          />
          {' '}
          Only show products in stock
        </p>
      </form>
    );
  }
}

class FilterableProductTable extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      filterText: '',
      inStockOnly: false
    };
//function bind this https://stackoverflow.com/questions/2236747/use-of-the-javascript-bind-method
// make this two function bind to searchBars props
    this.handleFilterTextChange = this.handleFilterTextChange.bind(this);
    this.handleInStockChange = this.handleInStockChange.bind(this);
  }

  handleFilterTextChange(filterText) {
    this.setState({
      filterText: filterText
    });
  }

  handleInStockChange(inStockOnly) {
    this.setState({
      inStockOnly: inStockOnly
    })
  }

  render() {
    return (
      <div>
        <SearchBar
          filterText={this.state.filterText}
          inStockOnly={this.state.inStockOnly}
          onFilterTextChange={this.handleFilterTextChange}
          onInStockChange={this.handleInStockChange}
        />
        <ProductTable
          products={this.props.products}
          filterText={this.state.filterText}
          inStockOnly={this.state.inStockOnly}
        />
      </div>
    );
  }
}


const PRODUCTS = [
  {category: 'Sporting Goods', price: '$49.99', stocked: true, name: 'Football'},
  {category: 'Sporting Goods', price: '$9.99', stocked: true, name: 'Baseball'},
  {category: 'Sporting Goods', price: '$29.99', stocked: false, name: 'Basketball'},
  {category: 'Electronics', price: '$99.99', stocked: true, name: 'iPod Touch'},
  {category: 'Electronics', price: '$399.99', stocked: false, name: 'iPhone 5'},
  {category: 'Electronics', price: '$199.99', stocked: true, name: 'Nexus 7'}
];

ReactDOM.render(
  <FilterableProductTable products={PRODUCTS} />,
  document.getElementById('container')
);

组合/继承

容器包含

组件比如滚动条,对话框,并不能预先知道有哪些孩子。 此类组件的推荐做法是, 使用特殊的prop来直接把孩子传进他们的输出。

function FancyBorder(props) {
  return (
    <div className={'FancyBorder FancyBorder-' + props.color}>
      {props.children}
    </div>
  );
}
function WelcomeDialog() {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        Welcome
      </h1>
      <p className="Dialog-message">
        Thank you for visiting our spacecraft!
      </p>
    </FancyBorder>
  );
}
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 />
      } />
  );
}

特定形式

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

组件形式

function Dialog(props) {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        {props.title}
      </h1>
      <p className="Dialog-message">
        {props.message}
      </p>
      {props.children}
    </FancyBorder>
  );
}
class SignUpDialog extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.handleSignUp = this.handleSignUp.bind(this);
    this.state = {login: ''};
  }
  render() {
    return (
      <Dialog title="Mars Exploration Program"
              message="How should we refer to you?">
        <input value={this.state.login}
               onChange={this.handleChange} />
        <button onClick={this.handleSignUp}>
          Sign Me Up!
        </button>
      </Dialog>
    );
  }
  handleChange(e) {
    this.setState({login: e.target.value});
  }

  handleSignUp() {
    alert(`Welcome aboard, ${this.state.login}!`);
  }
}

quickstarthttps://reactjs.org/docs/introducing-jsx.html

为什么用jsx

React 认为渲染逻辑本质上与其他的UI逻辑是耦合的。比如事件处理,状态变化以及数据显示之前的准备等。 不是人工去分离技术,把标记和逻辑分开存放。React把关注分开成松耦合的组件。组件同时包含两者。

  • 可以嵌入{表达式}。 JSX外面用()包起来,避免自动分号插入
  • 本身是表达式。编译之后jsx变成函数调用,并计算成javascript对象。就是说可以赋值给变量,作参数,或者从函数返回
  • 用jsx指定属性。attr=“字符串”或者attr={expression}, DONT不要“{expression}”
  • 因为jsx更接近javascript, 而不是html,属性的名字使用Javascript驼峰规范,比如tabindex变成tabIndex,class变成className
  • 防止注入攻击。渲染之前,reactdom会把嵌入jsx中的所有值转义,因此可以放心放入应用之外的内容,防止XSS(crossSiteScripting).
  • jsx代表对象。最终编译成一个函数React.createElement()调用的表达式。最终的对象叫react元素,可以看作一个显示的描述,react读入对象,构建并更新DOM。
//demo for 1
function formatName(user) {
  return user.firstName + ' ' + user.lastName;
}

const user = {
  firstName: 'Harper',
  lastName: 'Perez'
};

const element = (
  <h1>
    Hello, {formatName(user)}!
  </h1>
);

ReactDOM.render(
  element,
  document.getElementById('root')
);
//demo for 2
function getGreeting(user) {
  if (user) {
    return <h1>Hello, {formatName(user)}!</h1>;
  }
  return <h1>Hello, Stranger.</h1>;
}
//demo for 5
const title = response.potentiallyMaliciousInput;
// This is safe:
const element = <h1>{title}</h1>;
//demo for 6
const element = (
  <h1 className="greeting">
    Hello, world!
  </h1>
);
//等价于
const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);
//最终对象
const element = {
  type: 'h1',
  props: {
    className: 'greeting',
    children: 'Hello, world!'
  }
};

把元素渲染成DOM

元素是reactapp的最小构建单元。组件由元素构成。 与DOM元素不同,react元素是javascript对象。react-dom负责更新dom来匹配react元素。

提交一个react元素到dom

假定有一个html元素<div id="root"></div>,我们称他为根dom节点,因为里面所有的内容都是react-dom管理。 react应用一般只有一个根节点。如果集成react到其他app,你可以有多个根节点。

更新渲染的元素

React元素是不可变更immutable的. 一旦元素创建好就不能更改其孩子,属性。就像电影中的帧。 因此,更新UI唯一的方法就是创建新元素并传给ReactDOM.render()方法

function tick() {
  const element = (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {new Date().toLocaleTimeString()}.</h2>
    </div>
  );
  ReactDOM.render(element, document.getElementById('root'));
}
setInterval(tick, 1000);

一般实际中只调用render一次,我们要把这样的逻辑封装进stateful组件。

组件和Props

组件把UI分割成独立可重用的片,独立看待每个片。概念上像javascript函数,接受输入Props,返回React元素。

  • 组件最简单的定义方法是用函数,这种方式叫函数组件,因为其本质是javascript函数。
  • ES6的方式定义
  • 组件大写开头
  • 函数有两种,一种是pure,不改变参数内容,另一种会改变。react组件必须pure,不能改变props也就是说props是只读的.当然,UI总是不停在变,此时就要引入state.
  • state功能只有组件才有
//demo for 1
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}
//demo for 2

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}
ReactDOM.render(
  (<Welcome name="Sara" />),
  document.getElementById('root')
);

State和lifecycle

  • setState({}),不要直接设置state.xxx=yyy
  • 使用之前的值,用this.setState((prevState, props) => ({counter: prevState.counter + props.increment}));方式
  • jsx可以直接用state的值<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
  • 在函数组件,FormattedDate不知道这个值从Clock的哪里来,手写的,还是state,还是props。这个特性称作单向数据流或者自顶向下数据流。state属于某个特定组件,任意从该state获取的数据或者UI触发只影响其下面的组件。
  • 如果把组件树看作props的瀑布,那么state就像另外一处水源,在任意点加入并跟着一起向下流。
//demo for 4
function FormattedDate(props) {
  return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}
<FormattedDate date={this.state.date} />

//whole sample
class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }
  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }
  componentWillUnmount() {
    clearInterval(this.timerID);
  }
  tick() {
    this.setState({
      date: new Date()
    });
  }
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}
ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

事件处理

  • 句法区别,1,事件名字驼峰命名,2,传入函数而不是字符串
  • 不能用return false来阻止默认行为,要明确调用preventDefault
  • 不用addEventListener来给DOM元素添加listener, 在元素初始渲染的时候提供listener.
  • 关于上一条中的this。javascript中类方法缺省情况下,是没有绑定的。如果不做绑定,调用的时候handleClick函数的this就会变成undefined. 可以使用()=>{}箭头函数
  • 给eventHandler传递参数。比如传入一个rowId,下面两种方式是一样的。
    • <button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
    • <button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
// demo for 1
<button onclick="activateLasers()">
  Activate Lasers
</button>
<button onClick={activateLasers}>
  Activate Lasers
</button>

//demo for 2
<a href="#" onclick="console.log('The link was clicked.'); return false">
  Click me
</a>
function ActionLink() {
  function handleClick(e) {
    e.preventDefault();
    console.log('The link was clicked.');
  }
  return (
    <a href="#" onClick={handleClick}>
      Click me
    </a>
  );
}

//demo for 3
class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};
    // This binding is necessary to make `this` work in the callback, last this is button object in this case
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(prevState => ({
      isToggleOn: !prevState.isToggleOn
    }));
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}
ReactDOM.render(
  <Toggle />,
  document.getElementById('root')
);

//demo for 4
class LoggingButton extends React.Component {
  handleClick() {
    console.log('this is:', this);
  }
  render() {
    // 保证handleClick内的this有绑定
    return (
      <button onClick={(e) => this.handleClick(e)}>
        Click me
      </button>
    );
  }
}

class LoggingButton extends React.Component {
  // This syntax ensures `this` is bound within handleClick.
  // Warning: this is *experimental* syntax.
  handleClick = () => {
    console.log('this is:', this);
  }
  render() {
    return (
      <button onClick={this.handleClick}>
        Click me
      </button>
    );
  }
}

条件渲染

react中,可以有条件的渲染内容,创建不同的组件来封装行为。

比如有两个组件,可以用props来有条件的渲染。

function UserGreeting(props) {
  return <h1>Welcome back!</h1>;
}
function GuestGreeting(props) {
  return <h1>Please sign up.</h1>;
}


function Greeting(props) {
  const isLoggedIn = props.isLoggedIn;
  if (isLoggedIn) {
    return <UserGreeting />;
  }
  return <GuestGreeting />;
}
ReactDOM.render(
  // Try changing to isLoggedIn={true}:
  <Greeting isLoggedIn={false} />,
  document.getElementById('root')
);

元素变量

用变量来存储element,来做有条件的提交。

function LoginButton(props) {
  return (
    <button onClick={props.onClick}>
      Login
    </button>
  );
}
function LogoutButton(props) {
  return (
    <button onClick={props.onClick}>
      Logout
    </button>
  );
}

class LoginControl extends React.Component {
  constructor(props) {
    super(props);
    this.handleLoginClick = this.handleLoginClick.bind(this);
    this.handleLogoutClick = this.handleLogoutClick.bind(this);
    this.state = {isLoggedIn: false};
  }
  handleLoginClick() {
    this.setState({isLoggedIn: true});
  }
  handleLogoutClick() {
    this.setState({isLoggedIn: false});
  }
  render() {
    const isLoggedIn = this.state.isLoggedIn;
    const button = isLoggedIn ? (
      <LogoutButton onClick={this.handleLogoutClick} />
    ) : (
      <LoginButton onClick={this.handleLoginClick} />
    );
    return (
      <div>
        <Greeting isLoggedIn={isLoggedIn} />
        {button}
      </div>
    );
  }
}
ReactDOM.render(
  <LoginControl />,
  document.getElementById('root')
);

可以使用{表达式}的内置条件来条件渲染

//使用&&
function Mailbox(props) {
  const unreadMessages = props.unreadMessages;
  return (
    <div>
      <h1>Hello!</h1>
      {unreadMessages.length > 0 &&
        <h2>
          You have {unreadMessages.length} unread messages.
        </h2>
      }
    </div>
  );
}
const messages = ['React', 'Re: React', 'Re:Re: React'];
ReactDOM.render(
  <Mailbox unreadMessages={messages} />,
  document.getElementById('root')
);
//使用if/else
render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    <div>
      The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in.
    </div>
  );
}
render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    <div>
      {isLoggedIn ? (
        <LogoutButton onClick={this.handleLogoutClick} />
      ) : (
        <LoginButton onClick={this.handleLoginClick} />
      )}
    </div>
  );
}

阻止渲染

尽管被其他组件渲染了,但希望隐藏起来,通过return null来做

function WarningBanner(props) {
  // return props.warn?null:(<div className="warning">Warning!</div>);
  if (!props.warn) {
    return null;
  }
  return (
    <div className="warning">
      Warning!
    </div>
  );
}

class Page extends React.Component {
  constructor(props) {
    super(props);
    this.state = {showWarning: true};
    this.handleToggleClick = this.handleToggleClick.bind(this);
  }

  handleToggleClick() {
    this.setState(prevState => ({
      showWarning: !prevState.showWarning
    }));
  }
  render() {
    return (
      <div>
        <WarningBanner warn={this.state.showWarning} />
        <button onClick={this.handleToggleClick}>
          {this.state.showWarning ? 'Hide' : 'Show'}
        </button>
      </div>
    );
  }
}
ReactDOM.render(
  <Page />,
  document.getElementById('root')
);

List和Key

和javascript基本一样

const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((number) => number * 2);
console.log(doubled);
//react
function NumberList(props) {
  const numbers = props.numbers;
  //key is needed for creating list of elements.
  const listItems = numbers.map((number) =>
    <li key={number.toString()}>{number}</li>
  );
  return (
    <ul>{listItems}</ul>
  );
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);
//最好选择能够区分的key,如果传入的对象有id,用id,实在没有,用默认的
const todoItems = todos.map((todo) =>
  <li key={todo.id}>
    {todo.text}
  </li>
);
//不推荐使用默认的,可能影响性能,而且对state有影响引起问题。[参考](https://medium.com/@robinpokorny/index-as-a-key-is-an-anti-pattern-e0349aece318)
const todoItems = todos.map((todo, index) =>
  // Only do this if items have no stable IDs
  <li key={index}>
    {todo.text}
  </li>
);

用key提取组件

如果要提取ListItem组件,key就要放在<ListItem />上,而不是<li>

function ListItem(props) {
  const value = props.value;
  return (
    // Wrong! There is no need to specify the key here:
    <li key={value.toString()}>
      {value}
    </li>
  );
}
function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    // Wrong! The key should have been specified here:
    <ListItem value={number} />
  );
  return (
    <ul>
      {listItems}
    </ul>
  );
}

function ListItem(props) {
  // Correct! There is no need to specify the key here:
  return <li>{props.value}</li>;
}

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    // Correct! Key should be specified inside the array.
    <ListItem key={number.toString()}
              value={number} />
  );
  return (
    <ul>
      {listItems}
    </ul>
  );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);

表单

html的表单元素input,textarea,select有自己的状态,基于用户的输入来更新。 我们可以把其状态与react合并,由react来控制表单元素的状态。如果输入表单元素的值由react控制,就称之为controlled element. 因为controlled element比较繁琐,如要所有的数据变更都要写eventHandler传给state, 可以考虑用高级教程中的uncontrolled element.

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('A name was submitted: ' + this.state.value);
    event.preventDefault();
  }
  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input type="text" value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

状态向上走

两个input的状态通过handler函数委托给props的函数向上走。由上面的组件执行更新state的任务,并把得到的结果通过props返回。

const scaleNames = {
  c: 'Celsius',
  f: 'Fahrenheit'
};
function toCelsius(fahrenheit) {
  return (fahrenheit - 32) * 5 / 9;
}
function toFahrenheit(celsius) {
  return (celsius * 9 / 5) + 32;
}
function tryConvert(temperature, convert) {
  const input = parseFloat(temperature);
  if (Number.isNaN(input)) {
    return '';
  }
  const output = convert(input);
  const rounded = Math.round(output * 1000) / 1000;
  return rounded.toString();
}
function BoilingVerdict(props) {
  if (props.celsius >= 100) {
    return <p>The water would boil.</p>;
  }
  return <p>The water would not boil.</p>;
}
class TemperatureInput extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
  }
  handleChange(e) {
    this.props.onTemperatureChange(e.target.value);
  }
  render() {
    const temperature = this.props.temperature;
    const scale = this.props.scale;
    return (
      <fieldset>
        <legend>Enter temperature in {scaleNames[scale]}:</legend>
        <input value={temperature}
               onChange={this.handleChange} />
      </fieldset>
    );
  }
}

class Calculator extends React.Component {
  constructor(props) {
    super(props);
    this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
    this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
    this.state = {temperature: '', scale: 'c'};
  }
  handleCelsiusChange(temperature) {
    this.setState({scale: 'c', temperature});
  }
  handleFahrenheitChange(temperature) {
    this.setState({scale: 'f', temperature});
  }
  render() {
    const scale = this.state.scale;
    const temperature = this.state.temperature;
    const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
    const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;
    return (
      <div>
        <TemperatureInput
          scale="c"
          temperature={celsius}
          onTemperatureChange={this.handleCelsiusChange} />
        <TemperatureInput
          scale="f"
          temperature={fahrenheit}
          onTemperatureChange={this.handleFahrenheitChange} />
        <BoilingVerdict
          celsius={parseFloat(celsius)} />
      </div>
    );
  }
}
ReactDOM.render(
  <Calculator />,
  document.getElementById('root')
);

results matching ""

    No results matching ""