react(二)

一、es6

  • 对象的扩展

a)属性名表达式

ES6 允许字面量定义对象时,用表达式作为对象的属性名,即把表达式放在方括号内。

var p = 'key1';
var obj = {
  [p]: 'value1'
};
var a = 2;
obj['key'+a] = 'value2';
console.log(obj); // { key1: "value1", key2: "value2" }

b)属性的简洁表示法

var x = 1;
var obj = {
  x,
  y() {
    console.log('对象方法的简写')
  }
};
console.log(obj); // { x: 1, y: function y() { console.log('对象方法的简写'); } }

上面代码表明,ES6 允许在对象之中,直接写变量。这时,属性名为变量名, 属性值为变量的值。除了属性简写,方法也可以简写。

  • 字符串的扩展

a)模板字符串

模板字符串是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量

let s1 = `hello`;
let s2 = `In JavaScript this is
          not legal.`;
let name = "Bob", time = "today";
let s3 = `Hello ${name}, how are you ${time}?`;
console.log(s1);
console.log(s2);
console.log(s3);

运行结果:

模板字符串中嵌入变量,需要将变量名写在${}之中

大括号内部可以放入任意的 JavaScript 表达式,可以进行运算,以及引用对象属性。

let obj = {x: 1, y: 2};
console.log(`${obj.x + obj.y}`); // 3

模板字符串之中还能调用函数。

function fn() {
  return "Hello World";
}
console.log(`foo ${fn()} bar`); // foo Hello World bar

 

二、react

  • 处理事件

通过 React 元素处理事件跟在 DOM 元素上处理事件非常相似。但是有一些语法上的区别:

React 事件使用驼峰命名,而不是全部小写;通过 JSX , 你传递一个函数作为事件处理程序,而不是一个字符串;在 React 中你不能通过返回 false来阻止默认行为。必须明确调用 preventDefault 。

当使用 React 时,你一般不需要调用 addEventListener 在 DOM 元素被创建后添加事件监听器。相反,只要当元素被初始渲染的时候提供一个监听器就可以了。

<div id="root"></div>
<a href="#" onclick="return false">Click me</a>
function ActionLink() {
    function handleClick(e) {
     // 这里,e 是一个合成的事件。 React 根据 W3C 规范 定义了这个合成事件,所以不需要担心跨浏览器的兼容性问题。 e.preventDefault(); console.log(
'The link was clicked.'); // return false; // 此时不能阻止默认行为 } return ( <a href="#" onClick={handleClick}>click me</a> ); } ReactDOM.render( <ActionLink />, document.getElementById('root') );

当使用一个 ES6 类 定义一个组件时,通常的一个事件处理程序是类上的一个方法

class Toggle extends React.Component {
  constructor() {
    super();
    this.state = {isToggleOn: true};  
    this.handleClick = this.handleClick.bind(this);// 这个绑定使`this`在回调中起作用,不绑定handleClick方法里的 this 会是 undefined
  }
  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')
);

在JSX回调中你必须注意 this 的指向。 在 JavaScript 中,类方法默认没有 绑定 的。如果你忘记绑定 this.handleClick 并将其传递给onClick,那么在直接调用该函数时,this 会是 undefined 。(bind()方法会创建一个新函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以创建它时传入 bind()方法的第一个参数作为 this)

将参数传递给事件处理程序

方式一:bind

class Toggle extends React.Component {
  constructor() {
    super();
    this.state = {isToggleOn: true}; 
  }
  handleClick(param, e) {
    this.setState(prevState => ({
      isToggleOn: !prevState.isToggleOn
    }));
    console.log(e);
    console.log(param);
  }
  render() {
    return (<button onClick={this.handleClick.bind(this, 1)}>{this.state.isToggleOn ? 'ON' : 'OFF'}</button>);
  }
}
ReactDOM.render(
  <Toggle />,
  document.getElementById('root')
);

方式二:箭头函数

render() {
  return (<button onClick={(e) => this.handleClick(1, e)}>{this.state.isToggleOn ? 'ON' : 'OFF'}</button>);
}

在循环内部,通常需要将一个额外的参数传递给事件处理程序。上面两个例子中,参数 e 作为 React 事件对象将会被作为第二个参数进行传递。通过箭头函数的方式,事件对象必须显式的进行传递,但是通过 bind 的方式,事件对象以及更多的参数将会被隐式的进行传递。

  • 条件渲染
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(
  // 修改为 isLoggedIn={true} 试试:
  <Greeting isLoggedIn={false} />,
  document.getElementById('root')
);
  • 列表渲染

通常情况下,我们会在一个组件中渲染列表。

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map(number => <li>{number}</li>);
  return (<ul>{listItems}</ul>);
}

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

当运行上述代码的时候,将会收到一个警告(Warning: Each child in an array or iterator should have a unique "key" prop.)当创建元素列表时,“key” 是一个你需要包含的特殊字符串属性。

键(Keys) 帮助 React 标识哪个项被修改、添加或者移除了。数组中的每一个元素都应该有一个唯一不变的键来标识。挑选 key 最好的方式是使用一个在它的同辈元素中不重复的标识字符串。多数情况你可以使用数据中的 IDs 作为 keys。当要渲染的列表项中没有稳定的 IDs 时,你可以使用数据项的索引值作为 key 的最后选择。如果列表项可能被重新排序时,我们不建议使用索引作为 keys,因为这导致一定的性能问题,会很慢。

function ListItem(props) {
  return <li>{props.value}</li>;
}
function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map(number => <ListItem key={number} value={number} />);
  return (<ul>{listItems}</ul>);
}
const numbers = [1,2,3,4,5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);

JSX允许在大括号中嵌入任何表达式。

function NumberList(props) {
  const numbers = props.numbers;
  return (
    <ul>
      {numbers.map(number => <ListItem key={number} value={number} />)}
    </ul>
  );
}

就像在 JavaScript 中,是否有必要提取一个变量以提高程序的可读性,这取决于你。但是记住,如果 map() 体中有太多嵌套,可能是提取组件的好时机。

 

  • 表单

a)在 HTML 中,表单元素如 <input><textarea> 和 <select> 通常保持自己的状态,并根据用户输入进行更新。而在 React 中,可变状态一般保存在组件的 state(状态) 属性中,并且只能通过 setState() 更新。我们可以通过使 React 的 state 成为 “单一数据源原则” 来结合这两个形式。然后渲染表单的 React 组件也可以控制在用户输入之后的行为。这种形式,其值由 React 控制的输入表单元素称为“受控组件”

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>
    );
  }
}
ReactDOM.render(
  <NameForm />,
  document.getElementById('root')
);

设置表单元素的value属性之后,其显示值将由this.state.value决定,以满足React状态的同一数据理念。每次键盘敲击之后会执行handleChange方法以更新React状态,显示值也将随着用户的输入改变。对于受控组件来说,每一次 state 变化都会伴有相关联的处理函数,这使得可以直接修改或验证用户的输入。比如,如果我们希望强制 name 的输入都是大写字母,可以这样来写 handleChange 方法:

handleChange(event) {
  this.setState({value: event.target.value.toUpperCase()});
}

b)在 React 中,一个 <input type =“file”/> 标签的 value 属性是只读的, 所以它是 React 中的一个非受控组件。它的值只能由用户设置,而不是以编程方式设置。

class FileInput extends React.Component {
  constructor(props) {
    super(props);
    this.handleSubmit = this.handleSubmit.bind(this);
  }
  handleSubmit(event) {
    event.preventDefault();
    alert(
      `Selected file - ${
        this.fileInput.files[0].name
      }`
    );
  }
  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Upload file:
          <input type="file" ref={input => { this.fileInput = input; }} />
        </label>
        <br />
        <button type="submit">Submit</button>
      </form>
    );
  }
}

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

c)处理多个输入元素

当您需要处理多个受控的 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.type === 'checkbox' ? target.checked : target.value;
    const name = target.name;
  // ES6 允许字面量定义对象时,用方法二(表达式)作为对象的属性名,即把表达式放在方括号内
    this.setState({
      [name]: value
    });
  }

  render() {
    return (
      <form>
        <label>
          Is going:<input name="isGoing" type="checkbox" checked={this.state.isGoing} onChange={this.handleInputChange} />
        </label>
        <br />
        <label>
          Number of guests:<input name="numberOfGuests" type="number" value={this.state.numberOfGuests} onChange={this.handleInputChange} />
        </label>
      </form>
    );
  }
}
ReactDOM.render(
  <Reservation />,
  document.getElementById('root')
);

d)受控组件和不受控组件

在大多数情况下,我们推荐使用受控组件来实现表单。在受控组件中,表单数据由 React 组件负责处理。另外一个选择是不受控组件,其表单数据由 DOM 元素本身处理。要编写一个未控制组件,你可以使用一个 ref 来从 DOM 获得 表单值,而不是为每个状态更新编写一个事件处理程序

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.handleSubmit = this.handleSubmit.bind(this);
  }
  handleSubmit(event) {
    alert('A name was submitted: ' + this.input.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:<input type="text" ref={(input) => this.input = input} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}
ReactDOM.render(
  <NameForm />,
  document.getElementById('root')
);
  •  状态提升

在 React 中,共享 state(状态) 是通过将其移动到需要它的组件的最接近的共同祖先组件来实现的,这被称为“状态提升。

在一个 React 应用中,对于任何可变的数据都应该循序“单一数据源”原则。通常情况下,state 首先被添加到需要它进行渲染的组件。然后,如果其它的组件也需要它,你可以提升状态到它们最近的祖先组件。你应该依赖 从上到下的数据流向 ,而不是试图在不同的组件中同步状态。

 

  • 组合 VS 继承

React 拥有一个强大的组合模型,我们建议使用组合而不是继承以实现代码的重用。

a)一些组件在设计前无法获知自己要使用什么子组件,尤其在 Sidebar 和 Dialog 等通用 “容器” 中比较常见。我们建议这种组件使用特别的 children prop 来直接传递 子元素到他们的输出中(允许其他组件通过嵌套 JSX 传递任意子组件)。

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>
  );
}
ReactDOM.render(
  <WelcomeDialog />,
  document.getElementById('root')
);

浏览器渲染结果:

b)有时候,在一个组件中你可能需要多个 “占位符” 。在这种情况下,你可以使用自定义的 prop(属性),而不是使用 children

function Contacts() {
  return <div className="Contacts">Contacts</div>;
}
function Chat() {
  return <div className="Chat">Chat</div>;
}
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 /> } /> );
}
ReactDOM.render(
  <App />,
  document.getElementById('root')
);

浏览器渲染结果:

<Contacts /> 和 <Chat /> 等 React 元素本质上也是对象,所以可以将其像其他数据一样作为 props(属性) 传递使用。

c)有时候,我们考虑组件作为其它组件的“特殊情况”。例如,我们可能说一个 WelcomeDialog 是 Dialog 的一个特殊用例。在React中,也可以使用组合来实现,一个偏“特殊”的组件渲染出一个偏“通用”的组件,通过 props(属性) 配置它

function FancyBorder(props) {
  return (
    <div className={'FancyBorder FancyBorder-' + props.color}>
      {props.children}
    </div>
  );
}
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" />;
}
ReactDOM.render(
  <WelcomeDialog />,
  document.getElementById('root')
);

浏览器渲染结果:

 

  • React 的编程思想

 

在线框图中,出现在其它组件内的组件,应该在层次结构中显示为子组件即可

步骤1:将 UI 拆解到组件层次结构中

如何拆分组件 - 一个常用的技巧是单一职责原则,即一个组件理想情况下只处理一件事。如果一个组件持续膨胀,就应该将其拆分为多个更小的组件中

步骤2: 用 React 构建一个静态版本

  要构建你 app 的一个静态版本,用于渲染数据模型, 您将需要构建复用其他组件并使用 props传递数据的组件。props 是将数据从 父级组件 传递到 子级 的一种方式。在构建静态版本时 *不要使用 *state ** 。state 只用于交互,也就是说,数据可以随时被改变。由于这是一个静态版本 app,所以你并不需要使用 state 。

  您可以 自上而下 或 自下而上 构建。也就是说,您可以从构建层次结构中顶端的组件开始(即从 FilterableProductTable 开始),也可以从构建层次结构中底层的组件开始(即 ProductRow )。在更简单的例子中,通常 自上而下 更容易,而在较大的项目中,自下而上,更有利于编写测试

  在这一步结束时,你已经有了一个可重用的组件库,用于渲染你的数据模型。组件将只有 render() 方法,因为这是你应用程序的静态版本。层次结构顶部的组件( FilterableProductTable )应该接收你的数据模型作为 prop 。如果您对基础数据模型进行更改,并再次调用 ReactDOM.render(),UI 将同步更新。

步骤3: 确定 UI state(状态) 的最小(但完整)表示

  为了你的 UI 可以交互,你需要能够触发更改底层的数据模型。React 通过 state 使其变得容易。要正确的构建应用程序,你首先需要考虑你的应用程序需要的可变 state(状态) 的最小集合。这里的关键是:不要重复你自己 (DRY,don’t repeat yourself)。找出你的应用程序所需 state(状态) 的绝对最小表示,并且可以以此计算出你所需的所有其他数据内容。例如,如果你正在构建一个 TODO 列表,只保留一个 TODO 元素数组即可;不需要为元素数量保留一个单独的 state(状态) 变量。相反,当你要渲染 TODO 计数时,只需要获取 TODO 数组的长度即可

  原始的产品列表作为 props(属性) 传递,所以它不是 state(状态) 。搜索文本和复选框似乎是 state(状态) ,因为它们会根据用户的输入发生变化,并且不能从其他数据计算得出。 最后,过滤后的产品列表不是 state(状态) ,因为它可以通过结合 原始产品列表 与 搜索文本 和 复选框的值 计算得出。

 => 

步骤4:确定 state(状态) 的位置

  已经决定 state(状态) 保存在 FilterableProductTable 中。首先,添加一个实例属性 this.state = {filterText: '', inStockOnly: false} 到 FilterableProductTable 的constructor 来反映你应用的初始 state(状态) 。然后,传递 filterText 和 inStockOnly 到 ProductTable 和 SearchBar 作为一个 prop(属性) 。最后,使用这些 props(属性) 来过滤 ProductTable 中的行,并设置 SearchBar 中的表单字段的值。

步骤5:添加反向数据流

  目前,构建的应用已经具备了正确渲染 props(属性) 和 state(状态) 沿着层次结构向下传播的功能。现在是时候实现另一种数据流方式:层次结构中深层的 form(表单) 组件需要更新 FilterableProductTable 中的 state(状态) 。React 中明确的数据流向,使你容易理解程序如何运行。但是相比传统的数据双向绑定来说,的确需要多敲一些代码。如果你尝试在当前版本中的例子中输入或勾选复选框,你发现 React 会忽略了你的输入。这是有意为之的,因为我们已经设置了 input 的 value prop(属性) 总是等于从 FilterableProductTable 中传递的 state 。我们可以使用 input 的 onChange 事件来接收通知。而且通过 FilterableProductTable 传递的回调调用 setState(),然后应用被更新

class ProductCategoryRow extends React.Component {
  render() {
    return (<tr><th colSpan="2">{this.props.category}</th></tr>);
  }
}
class ProductRow extends React.Component {
  render() {
    var name = this.props.product.stocked ?
      this.props.product.name :
      <span style={{color: 'red'}}>
        {this.props.product.name}
      </span>;
    return (
      <tr>
        <td>{name}</td>
        <td>{this.props.product.price}</td>
      </tr>
    );
  }
}

class ProductTable extends React.Component {
  render() {
    var rows = [];
    var lastCategory = null;
    this.props.products.forEach((product) => {
      if (product.name.indexOf(this.props.filterText) === -1 || (!product.stocked && this.props.inStockOnly)) {
        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);
    this.handleFilterTextInputChange = this.handleFilterTextInputChange.bind(this);
    this.handleInStockInputChange = this.handleInStockInputChange.bind(this);
  } 
  handleFilterTextInputChange(e) {
    this.props.onFilterTextInput(e.target.value);
  } 
  handleInStockInputChange(e) {
    this.props.onInStockInput(e.target.checked);
  } 
  render() {
    return (
      <form>
        <input type="text" placeholder="Search..." value={this.props.filterText} onChange={this.handleFilterTextInputChange} />
        <p>
          <input type="checkbox" checked={this.props.inStockOnly} onChange={this.handleInStockInputChange} />
          Only show products in stock
        </p>
      </form>
    );
  }
}

class FilterableProductTable extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      filterText: '',
      inStockOnly: false
    };    
    this.handleFilterTextInput = this.handleFilterTextInput.bind(this);
    this.handleInStockInput = this.handleInStockInput.bind(this);
  }
  handleFilterTextInput(filterText) {
    this.setState({
      filterText: filterText
    });
  } 
  handleInStockInput(inStockOnly) {
    this.setState({
      inStockOnly: inStockOnly
    })
  }
  render() {
    return (
      <div>
        <SearchBar filterText={this.state.filterText} inStockOnly={this.state.inStockOnly} onFilterTextInput={this.handleFilterTextInput} onInStockInput={this.handleInStockInput} />
        <ProductTable products={this.props.products} filterText={this.state.filterText} inStockOnly={this.state.inStockOnly} />
      </div>
    );
  }
}

var 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')
);

 

posted @ 2018-07-03 10:19  Colorful_coco  阅读(206)  评论(0编辑  收藏  举报