美文网首页让前端飞React.js
React上手 —— 基础篇

React上手 —— 基础篇

作者: yozosann | 来源:发表于2019-03-12 21:26 被阅读19次

适用范围:熟悉es6语法、最好是Vue开发者

环境:

这就其实指能快速run react 语法的环境:

  1. codepen:缺点是没有语法提示(或许是我自己不会配?)
  2. react脚手架
第一个项目:
ReactDOM.render(
  <h1>Hello, world!</h1>,
  document.getElementById('root')
);

JSX简介:

const element = <h1>Hello, world!</h1>;

它被称为 JSX, 一种 JavaScript 的语法扩展。 我们推荐在 React 中使用 JSX 来描述用户界面。JSX 乍看起来可能比较像是模版语言,但事实上它完全是在 JavaScript 内部实现的。

在 JSX 中使用表达式:

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')
);
function getGreeting(user) {
  if (user) {
    return <h1>Hello, {formatName(user)}!</h1>;
  }
  return <h1>Hello, Stranger.</h1>;
}

jsx可以作为参数 或者返回值。

JSX 属性

const element = <div tabIndex="0"></div>;

或者打括号定义:

const element = <img src={user.avatarUrl} />;

警告:
因为 JSX 的特性更接近 JavaScript 而不是 HTML , 所以 React DOM 使用 camelCase 小驼峰命名 来定义属性的名称,而不是使用 HTML 的属性名称。
例如,class 变成了 className,而 tabindex 则对应着 tabIndex

JSX 防注入攻击:会自动过滤

JSX 代表 Objects

Babel 转译器会把 JSX 转换成一个名为 React.createElement() 的方法调用。

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

组件&Props

React 元素都是immutable 不可变的。当元素被创建之后,你是无法改变其内容或属性的。一个元素就好像是动画里的一帧,它代表应用界面在某一时间点的样子。

根据我们现阶段了解的有关 React 知识,更新界面的唯一办法是创建一个新的元素,然后将它传入 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);

React 只会更新必要的部分

React DOM 首先会比较元素内容先后的不同,而在渲染过程中只会更新改变了的部分。
定义组件的两种方式:

函数定义:
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}
类定义:
class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

调用:

const element = <Welcome name="Sara" />;

在函数定义:props. 可以直接访问,而在类中需要this.props.

Props的只读性

props是单向数据流,在组件里不能修改。

State & 生命周期

定义为类的组件有一些额外的特性。局部状态就是如此:只能用于类的一个功能。
这样我们就可以定义一个状态:状态与属性十分相似,但是状态是私有的,完全受控于当前组件。

这里可以理解为Vue里组件的data。

<template>
   <div>{{ conut }}</div>
</template>
<script>
export default{
    data() {
      return { count: 1}  
  }
}
</script>

当count改变的时候,视图也会刷新,而这里就和react的state类型。

vue里的data是双向绑定的,但是react里不是

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

如何让时钟走起来?在生命周期函数中挂载方法:

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

关于setState()需要注意:
  1. 不能直接更新状态
// Wrong
this.state.comment = 'Hello';
  1. 状态更新是异步的
// Wrong
this.setState({
  counter: this.state.counter + this.props.increment,
});
// Correct
this.setState(function(prevState, props) {
  return {
    counter: prevState.counter + props.increment
  };
});
  1. 可以单独设置状态的某一个属性 而不是整个对象:
  constructor(props) {
    super(props);
    this.state = {
      posts: [],
      comments: []
    };
  }

componentDidMount() {
    fetchPosts().then(response => {
      this.setState({
        posts: response.posts
      });
    });

    fetchComments().then(response => {
      this.setState({
        comments: response.comments
      });
    });
  }

事件处理:

  • React事件绑定属性的命名采用驼峰式写法,而不是小写。
  • 如果采用 JSX 的语法你需要传入一个函数作为事件处理函数,而不是一个字符串(DOM元素的写法)

在 React 中另一个不同是你不能使用返回 false 的方式阻止默认行为。你必须明确的使用 preventDefault。

function ActionLink() {
  function handleClick(e) {
    e.preventDefault();
    console.log('The link was clicked.');
  }

  return (
    <a href="#" onClick={handleClick}>
      Click me
    </a>
  );
}

你必须谨慎对待 JSX 回调函数中的 this,类的方法默认是不会绑定 this 的。如果你忘记绑定 this.handleClick 并把它传入 onClick, 当你调用这个函数的时候 this 的值会是 undefined

constructor(props) {
    super(props);
    this.state = {isToggleOn: true};

    // This binding is necessary to make `this` work in the callback
    this.handleClick = this.handleClick.bind(this);
  }

这并不是 React 的特殊行为;它是函数如何在 JavaScript 中运行的一部分。通常情况下,如果你没有在方法后面添加 () ,例如 onClick={this.handleClick},你应该为这个方法绑定 this

如果使用 bind 让你很烦,这里有两种方式可以解决。如果你正在使用实验性的属性初始化器语法,你可以使用属性初始化器来正确的绑定回调函数:

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

如果没有属性初始化器语法:

class LoggingButton extends React.Component {
  handleClick() {
    console.log('this is:', this);
  }

  render() {
    // This syntax ensures `this` is bound within handleClick
    return (
      <button onClick={(e) => this.handleClick(e)}>
        Click me
      </button>
    );
  }
}

条件渲染:

if-else:
render() {
    const isLoggedIn = this.state.isLoggedIn;

    let button = null;
    if (isLoggedIn) {
      button = <LogoutButton onClick={this.handleLogoutClick} />;
    } else {
      button = <LoginButton onClick={this.handleLoginClick} />;
    }

    return (
      <div>
        <Greeting isLoggedIn={isLoggedIn} />
        {button}
      </div>
    );
  }
与运算符 && 和三目运算符:
function Mailbox(props) {
  const unreadMessages = props.unreadMessages;
  return (
    <div>
      <h1>Hello!</h1>
      {unreadMessages.length > 0 &&
        <h2>
          You have {unreadMessages.length} unread messages.
        </h2>
      }
    </div>
  );
}
render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    <div>
      {isLoggedIn ? (
        <LogoutButton onClick={this.handleLogoutClick} />
      ) : (
        <LoginButton onClick={this.handleLoginClick} />
      )}
    </div>
  );
}

if-else是语法不是表达式,{}可以填入任何表达式。所以可以用运算符&&,这点和vue也是一样的。但是需要注意的是&&前必须是Boolean类型不能类Boolean类型,比如0,空字符串''

如何阻止组件渲染?

在极少数情况下,你可能希望隐藏组件,即使它被其他组件渲染。让 render 方法返回 null 而不是它的渲染结果即可实现。

function WarningBanner(props) {
  if (!props.warn) {
    return null;
  }

  return (
    <div className="warning">
      Warning!
    </div>
  );
}

组件的 render 方法返回 null 并不会影响该组件生命周期方法的回调。例如,componentWillUpdate 和 componentDidUpdate 依然可以被调用。

列表 & Keys

渲染多个组件:

const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
  <li>{number}</li>
);

我们把整个listItems插入到ul元素中,然后渲染进DOM:

ReactDOM.render(
  <ul>{listItems}</ul>,
  document.getElementById('root')
);

当我们运行这段代码,将会看到一个警告 a key should be provided for list items ,意思是当你创建一个元素时,必须包括一个特殊的 key 属性。

Keys

Keys可以在DOM中的某些元素被增加或删除的时候帮助React识别哪些元素发生了变化。因此你应当给数组中的每一个元素赋予一个确定的标识。

熟悉Vue这里就能直接理解

const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
  <li key={number.toString()}>
    {number}
  </li>
);

表单:

受控组件:在HTML当中,像<input>,<textarea>, 和 <select>这类表单元素会维持自身状态,并根据用户输入进行更新。但在React中,可变的状态通常保存在组件的状态属性中,并且只能用 setState() 方法进行更新。

在vue里我们有双向绑定:val变化视图变化,视图修改了val也变了。但是在react我们需要自己实现这一点,如何实现?
首先:

constructor(props) {
    super(props);
    this.state = {value: ''};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

<input type="text" value={this.state.value} onChange={this.handleChange} />

然后每次当input更改的时候调用this.handleChange方法

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

达到双向绑定的效果。

textarea 标签

在HTML当中,<textarea> 元素通过子节点来定义它的文本内容,
在React中,<textarea>会用value属性来代替。这样的话,表单中的<textarea> 非常类似于使用单行输入的表单:

render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <textarea value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }

select 标签

render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Pick your favorite La Croix flavor:
          <select value={this.state.value} onChange={this.handleChange}>
            <option value="grapefruit">Grapefruit</option>
            <option value="lime">Lime</option>
            <option value="coconut">Coconut</option>
            <option value="mango">Mango</option>
          </select>
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }

多个输入的解决方法
当你有处理多个受控的input元素时,你可以通过给每个元素添加一个name属性,来让处理函数根据 event.target.name的值来选择做什么。

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

状态提升

建议直接看文档能够清楚理解。
但是我们需要明白为什么这么写。
需求:InputA变了InputB也要,我们知道能改变的属性值只有内部属性state。生命在InputA中的state不能影响B。同理B也不能影响A。那么怎么解决呢?用一个容器组件C包裹InputA和InputB,容器组件C中的state就能自由改变,且作为props传给InputA和B,这样首先我们解决第一个问题:A、B共享可变数据。 再看需求我们并不是要C变了A、B改变,而是A变了B变,B变了C变,那么我们就需要解决A变了之后,把这个改变通知给C,交给C去改变,这样就能影响B了。
如何通知?类似Vue的事件机制:

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>在{scaleNames[scale]}:中输入温度数值</legend>
        <input value={temperature}
               onChange={this.handleChange} />
      </fieldset>
    );
  }
}

TemperatureInput在这里可以理解为是组件A或B。
通知机制就是:this.props.onTemperatureChange(e.target.value);
组件C:就在外部监视:

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

这样A、B变了,就通知到了C,C更新自己的state并传给A、B。

组合 vs 继承

组合:这里就类型Vue的slot:

包含关系

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

虽然不太常见,但有时你可能需要在组件中有多个入口,这种情况下你可以使用自己约定的属性而不是 children:(具名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 />
      } />
  );
}

React的基础概念就是这些,会了Vue基本上一天内搞定。

相关文章

网友评论

    本文标题:React上手 —— 基础篇

    本文链接:https://www.haomeiwen.com/subject/tnlopqtx.html