react 基础

react 基本使用和API

Posted by SkioFox on August 5, 2018

react 组件

  • class 组件

    React 的世界里一切皆是组件,我们使用class语法构建一个最基本的组件,组件的使用方式和HTML相同,组件的render函数返回页面渲染的一个JSX,然后使用ReactDom渲染到页面里

class App extends React.Component {
  render() {
    return <div> Hello React </div>
  }
}

ReactDOM.render(
  <App />,
  mountNode
)
  • 函数组件

    该函数是一个有效的 React 组件,因为它接收唯一带有数据的 “props”(代表属性)对象与并返回一个 React 元素。这类组件被称为“函数组件”,因为它本质上就是 JavaScript 函数。

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

属性传递

React组件使用和html类似的方式传递参数,在组件内部,使用this.props获取所有的传递的参数,在JSX里使用变量,使用{}包裹 组件无论是使用函数声明还是通过 class 声明,都决不能修改自身的 props。

class App extends React.Component {
  render() {
    return <div> Hello {this.props.name} </div>
  }
}

ReactDOM.render(
  <App name="React" />,
  mountNode
)

JSX

JSX是一种js的语法扩展,表面上像HTML,本质上还是通过babel转换为js执行,所以在JSX里我们是可以在{}中使用js的语法 JSX本质上就是转换为React.createElement在React内部构建虚拟Dom,最终渲染出页面

Babel 会把 JSX 转译成一个名为 React.createElement() 函数调用。Babel 会把 JSX 转译成一个名为 React.createElement() 函数调用,最终创建的是js对象,这些对象成为react元素。

与浏览器的 DOM 元素不同,React 元素是创建开销极小的普通对象。React DOM 会负责更新 DOM 来与 React 元素保持一致。

class App extends React.Component {
  render() {
    return (
      <div>
        Hello {this.props.name}, I am {2 + 2} years old
      </div>
    )
  }
}

ReactDOM.render(
  <App name="React" />, 
  mountNode
)

State和事件绑定

  • React内部通过this.state变量来维护内部的状态,并且通过this.setState来修改状态,render里用到的state变量,也会自动渲染到UI。
  • 我们现在constructor来初始化state,在JSX里使用this.state.num获取,然后jsx里使用onClick绑定点击事件。
    • 注意这里constructor里使用bind方法绑定this指向,然后内部调用this.setState修改值
    • 注意这里不能写成this.state.num+1,而是要调用this.setState,返回一个全新的num值
  • State 与 props 类似,但是 state 是私有的,并且完全受控于当前组件。
  • Class 组件应该始终使用 props 参数来调用父类的构造函数。
class Counter extends React.Component {
  constructor(props){
    super(props)
    // state必须写在constructor内,constructor才能初始化state
    this.state = {
      num:1
    }
    // 为了在回调中使用 `this`,这个绑定是必不可少的
    // 在 JavaScript 中,class 的方法默认不会绑定 this。
    // 如果你忘记绑定 this.handleClick 并把它传入了 onClick,当你调用这个函数的时候 this 的值为 undefined。
    this.handleClick = this.handleClick.bind(this)
    // 解决方案1
    // handleClick = () => {
    //   console.log('this is:', this);
    // }
    // 解决方案二,更改下面的render函数:箭头函数的this指向外部最近的执行上下文
  }
  handleClick(){
    // setState是异步的,每次生命周期过程会将所有的setState整合到一起进行更新
    //  setState可以传递一个对象或者一个函数
    // 1.传递一个对象时可以使用第二个参数cb函数获取最新的值
    this.setState({
      num:this.state.num+1
    },()=>{
      console.log(this.state.num)
    })
    // 2.传递函数时两个参数(prevState,prevProps)=>当更新值依赖于之前的状态或者属性时需要使用函数形式
    // this.state((prevState,prevProps)=>{
    //   // 必须返回一个想更新的对象
    //   return {
    //     num: prevState.count+1
    //   }
    // })
  }
  render() {
    return (
      <div>
        <p>{this.state.num}</p>
        <button onClick={this.handleClick}>click</button>
        {/*解决方法二=>缺点:如果该回调函数作为 prop 传入子组件时,这些组件可能会进行额外的重新渲染。*/}
        <button onClick={(e) => this.handleClick(e)}>
        {/*像事件处理程序传递参数*/}
        <button onClick={(e) => this.handleClick(e)}>Delete Row</button>
        <button onClick={this.handleClick.bind(this)}>Delete Row</button>
      </div>
    )
  }
}

ReactDOM.render(
  <Counter />,
  mountNode
)

生命周期

在组件内部可以声明一些特殊的方法,会在组件的不同阶段执行,比如组件加载完毕后会执行componentDidMount函数,组件更新的时候,会执行shouldComponentUpdate函数。 如下示例中如果shouldComponentUpdate返回true的话,就会一次执行componentWillMount、render,componentDidMount,如果返回false的话,就不会执行,代表组件的更新

class Counter extends React.Component {
  constructor(props){
    super(props)
    this.state = {
      num:1
    }
    this.handleClick = this.handleClick.bind(this)
  }
  componentDidMount(){
    console.log('componentDidMount 函数触发')
  }
  shouldComponentUpdate(nextProps,nextState){
    if (nextState.num%2) {
      return true
    }
    return false
  }
  // componentWillMount() {
  //   // 此时可以访问属性和状态了,可以进行api调用,但没办法做dom相关操作
  //   console.log("2.组件将要挂载");
  // }

  // componentDidMount() {
  //   // 组件已挂载,可进行状态更新操作
  //   console.log("3.组件已经挂载");
  //  componentDidMount() 方法会在组件已经被渲染到 DOM 中后运行,所以,最好在这里设置计时器:
  //  接下来把计时器的 ID 保存在 this 之中。尽管 this.props 和 this.state 是 React 本身设置的,且都拥有特殊的含义,但是其实你可以向 class 中随意添加不参与数据流(比如计时器 ID)的额外字段。
    // this.timerID = setInterval(
    //   () => this.tick(),
    //   1000
    // );
  // }

  // componentWillReceiveProps() {
  //   // 父组件传递的属性有变化,做相应响应
  //   console.log("4.组件属性更新了");
  // }

  // shouldComponentUpdate() {
  //   // 组件是否需要更新,返回布尔值,优化点
  //   console.log("5.组件是否应该更新?");
  //   return true;
  // }

  // componentWillUpdate() {
  //   console.log("6.组件将要更新");
  // }
  // componentDidUpdate() {
  //   console.log("7.组件已经更新");
  // }
  // 我们会在 componentWillUnmount() 生命周期方法中清除计时器:
  // componentWillUnmount() {
  //   clearInterval(this.timerID);
  // }
  handleClick(){
    this.setState({
      num:this.state.num+1
    })
  }
  render() {
    return (
      <div>
        <p>{this.state.num}</p>
        <button onClick={this.handleClick}>click</button>
      </div>
    )
  }
}
ReactDOM.render(
  <Counter />,
  mountNode
)

表单

一个常见的表单由form input label等标签构成,我们通过onChange和控制value的值,最终通过state,让原生的html输入内容和React链接起来

class TodoList extends React.Component {
  constructor(props){
    super(props)
    this.state = {
      text:''
    }
    this.handleClick = this.handleClick.bind(this)
    this.handleChange = this.handleChange.bind(this)
  }
  handleClick(){
    if (this.state.text) {
      this.setState({
        text:''
      })
    }
  }
  handleChange(e){
    this.setState({
      text:e.target.value
    })
  }
  render() {
    return (
      <div>
        {this.state.text}
        <input type="text" value={this.state.text} onChange={this.handleChange}/>
        <button onClick={this.handleClick}>clear</button>      
      </div>
    )
  }
}


ReactDOM.render(
  <TodoList />,
  mountNode
)

渲染列表

  • 页面里序列化的数据,比如用户列表,都是一个数组,我们通过map函数把数字直接映射为JSX,但是我们直接渲染列表,打开console的时候会看到Each child in an array or iterator should have a unique “key” prop.报错。
  • 渲染列表的时候,我们需要每个元素都有一个唯一的key属性,这样React在数据变化的时候,知道哪些dom应该发生变化 尤其注意key要唯一,使用每个字段唯一id,或者使用索引
  • 一个好的经验法则是:在 map() 方法中的元素需要设置 key 属性。
class TodoList extends React.Component {
  constructor(props){
    super(props)
    this.state = {
      todos:['Learn React','Learn Ant-design','Learn Koa'],
      text:''
    }
    this.handleClick = this.handleClick.bind(this)
    this.handleChange = this.handleChange.bind(this)
  }
  handleClick(){
    if (this.state.text) {
      this.setState(state=>({
        todos:[...state.todos,state.text],
        text:''
      }))
    }

  }
  handleChange(e){
    this.setState({
      text:e.target.value
    })
  }
  render() {
    return (
      <div>
        <input type="text" value={this.state.text} onChange={this.handleChange}/>
        <button onClick={this.handleClick}>add</button>
        <ul>
          {this.state.todos.map(v=>{
            // 一个好的经验法则是:在 map() 方法中的元素需要设置 key 属性。
            return <li key={v}>{v}</li>
          })}
        </ul>
      </div>
    )
  }
}


ReactDOM.render(
  <TodoList />,
  mountNode
)

状态提升

多个组件需要反映相同的变化数据,这时我们建议将共享状态提升到最近的共同父组件中去。

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>
    );
  }
}

当你对上述输入框内容进行编辑时会发生些什么?

  • React 会调用 DOM 中 的 onChange 方法。在本实例中,它是 TemperatureInput 组件的 handleChange 方法。
  • TemperatureInput 组件中的 handleChange 方法会调用 this.props.onTemperatureChange(),并传入新输入的值作为参数。其 props 诸如 onTemperatureChange 之类,均由父组件 Calculator 提供。
  • 起初渲染时,用于摄氏度输入的子组件 TemperatureInput 中 onTemperatureChange 方法为 Calculator 组件中的 handleCelsiusChange 方法,而,用于华氏度输入的子组件 TemperatureInput 中的 onTemperatureChange 方法为 Calculator 组件中的 handleFahrenheitChange 方法。因此,无论哪个输入框被编辑都会调用 Calculator 组件中对应的方法。
  • 在这些方法内部,Calculator 组件通过使用新的输入值与当前输入框对应的温度计量单位来调用 this.setState() 进而请求 React 重新渲染自己本身。
  • React 调用 Calculator 组件的 render 方法得到组件的 UI 呈现。温度转换在这时进行,两个输入框中的数值通过当前输入温度和其计量单位来重新计算获得。
  • React 使用 Calculator 组件提供的新 props 分别调用两个 TemperatureInput 子组件的 render 方法来获取子组件的 UI 呈现。
  • React 调用 BoilingVerdict 组件的 render 方法,并将摄氏温度值以组件 props 方式传入。
  • React DOM 根据输入值匹配水是否沸腾,并将结果更新至 DOM。我们刚刚编辑的输入框接收其当前值,另一个输入框内容更新为转换后的温度值。
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>
    );
  }
}
// 转换函数

function toCelsius(fahrenheit) {
  return (fahrenheit - 32) * 5 / 9;
}

function toFahrenheit(celsius) {
  return (celsius * 9 / 5) + 32;
}
// tryConvert('abc', toCelsius) 返回一个空字符串,而 tryConvert('10.22', toFahrenheit) 返回 '50.396'
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();
}

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

组合 vs 继承

  • React 有十分强大的组合模式。我们推荐使用组合而非继承来实现组件间的代码重用。
  • 有些组件无法提前知晓它们子组件的具体内容。在 Sidebar(侧边栏)和 Dialog(对话框)等展现通用容器(box)的组件中特别容易遇到这种情况。我们建议这些组件使用一个特殊的 children prop 来将他们的子组件传递到渲染结果中:
  • 有些时候,我们会把一些组件看作是其他组件的特殊实例,比如 WelcomeDialog 可以说是 Dialog 的特殊实例。在 React 中,我们也可以通过组合来实现这一点。“特殊”组件可以通过 props 定制并渲染“一般”组件:

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}!`);              
  }
}
  • Props 和组合为你提供了清晰而安全地定制组件外观和行为的灵活方式。注意:组件可以接受任意 props,包括基本数据类型,React 元素以及函数。
  • 如果你想要在组件间复用非 UI 的功能,我们建议将其提取为一个单独的 JavaScript 模块,如函数、对象或者类。组件可以直接引入(import)而无需通过 extend 继承它们。

    state 与 props区分

    在 React 中,有两类“模型”数据:props 和 state。清楚地理解两者的区别是十分重要的;

  • 通过问自己以下三个问题,你可以逐个检查相应数据是否属于 state:

    • 该数据是否是由父组件通过 props 传递而来的?如果是,那它应该不是 state。
    • 该数据是否随时间的推移而保持不变?如果是,那它应该也不是 state。
    • 你能否根据其他 state 或 props 计算出该数据的值?如果是,那它也不是 state。

react 应用

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=>
        {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);
    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
    };
    
    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')
);

React16新增了什么

  • Facebook 官方发布了 React 16.0。相较于之前的 15.x 版本,v16是第一个核心模块重写的版本,并且在异常处理,核心架构和服务端渲染方面都有更新。
    • render 函数支持返回数组和字符串
    • 异常处理,添加componentDidCatch钩子获取组件错误
    • 新的组件类型 portals 可以渲染当前容器dom之外的节点
    • 打包的文件体积减少 30%
    • 更换开源协议为MIT许可
    • Fiber架构,支持异步渲染
    • 更好的服务端渲染,支持字节流渲染

class React16 extends React.Component {
  constructor(props){
    super(props)
    this.state={hasError:false}
  }
  componentDidCatch(error, info) {
    this.setState({ hasError: true })
  }

  render() {
    return (
      <div>
        {this.state.hasError ? <div>出错了</div>:null}
        <ClickWithError />
        <FeatureReturnFragments />
      </div>
    )
  }
}

class ClickWithError extends React.Component{
   constructor(props){
    super(props)
    this.state = {error:false}
    this.handleClick = this.handleClick.bind(this)
  }
  handleClick(){
    this.setState({
      error:true
    })
  }
  render() {
    if (this.state.error) {
      throw new Error('出错了!')
    }
    return <button onClick={this.handleClick}>抛出错误</button>
  }
}
class FeatureReturnFragments extends React.Component{
  render(){
    return [
      <p key="key1">React很不错</p>,
      "文本1",
      <p key="key2">Antd-desing也很赞</p>,
      "文本2"
   ]
  }
}

ReactDOM.render(
  <React16 />,
  mountNode
)

官方脚手架

    npm install -g create-react-app
    create-react-app projectName