react基础

元素渲染

首先需要在HTML中添加一个根节点,在此节点中的内容都将由React DOM来管理。

<div id="root"></div>

接着通过ReactDOM.render() 的方法来将元素渲染到根DOM节点中。

const element = <h1>Hello, world</h1>;
ReactDOM.render(element, document.getElementById('root'));

React元素都是不可变的,当元素被创建之后是无法更改的。现阶段唯一更新页面的方法就是创建新元素然后通过ReactDOM.render() 更新。

组件&Props

组件

定义一个组件有两种方式,可以使用函数定义也可使用类定义,主要的区别方式就是一种是无状态组件一钟是有状态组件。后面会讲到这两种组件的区别。
函数定义:

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" />;

然后同样是使用ReactDOM.render() 的方法将组件渲染到视图上。

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

const element = <Welcome name="Sara" />;
ReactDOM.render(
  element,
  document.getElementById('root')
);

我们也可以在一个组件中嵌套另一个组件,通过这样的方式来实现一些更为丰富的内容。

Props

在刚才的例子我们使用到了props,props里的内容就是jsx的属性,通过props我们就可以实现父组件向子组件之间的通信。当props是只能读的,我们绝不能修改props的内容。

State & 生命周期

在最开始的时候我们更新视图只能重新创建元素再渲染,这里我们将介绍一种更为简便的方法。之前我们也曾提到过有状态和无状态组件,我们就是通过这个状态来实现视图的更新。
首先使用类定义一个组件

class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.props.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

接下来我们为这个组件添加一个状态。

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

注意到组件的构造函数中的super(props);,我们应始终使用props调用基础构造函数。

组件的生命周期

生命周期函数在react新版本中有了一些更改。将会移除
componentWillMount,componentWillReceiveProps,componentWillUpdate 这三个生命周期函数,新增两个生命周期函数 getDerivedStateFromProps,getSnapshotBeforeUpdate。

  • componentDidMount :在第一次渲染后调用,只在客户端。可以在这个方法中调用setTimeout, setInterval或者发送AJAX请求等操作。
  • shouldComponentUpdate :返回一个布尔值。在组件接收到新的props或者state时被调用。 可以在你确认不需要更新组件时使用。
  • componentDidUpdate: 在组件完成更新后立即调用。在初始化时不会被调用。
  • componentWillUnmount:在组件从 DOM 中移除之前立刻被调用。
  • getDerivedStateFromProps:在每次渲染之前都会调用,不管造成重新渲染的原因,不管初始挂载还是后面的更新都会调用。
  • getSnapshotBeforeUpdate:在最新的渲染数据提交给DOM前会立即调用,它让你在组件的数据可能要改变之前获取他们。

现在了解了react的生命周期函数之后,我们就可以在生命周期函数中添加一些操作,让我们的时钟能够每秒钟更新一次。

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

我们在组件渲染之后添加了一个定时器每秒执行一次tick()并使用set的方式更新视图,然后在组件卸载之前卸载了定时器。现在我们的时钟就能动起来了。值得注意的是我们直接修改state的值并不会引起视图上的改变,只能使用setState的方式重新渲染组件,而且构造函数式唯一能够初始化state的地方。

事件处理

在react中你需要传入一个函数作为时间处理函数,这和传统的DOM元素写法有写不同。

<button onClick={activateLasers}>
  Activate Lasers
</button>

在react中不能使用return false的方式阻止默认行为,必须使用preventDefault。

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

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

在react中类的方法默认不会绑定this的,当你没有绑定this时,调用this的值会是undefined。所以我们需要像这样绑定this。

this.handleClick = this.handleClick.bind(this);

当你觉得使用bind很麻烦的时候你可以使用实验性的属性初始化器语法 来正确的绑定回调函数:

  handleClick = () => {
    console.log('this is:', this);
  }

条件渲染

React使用JavaScript中的if和条件运算符来创建表示当前状态的元素,然后再来渲染。

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

这就是一个简单的例子。我们还可以使用变量来储存元素,使我们的代码变得更加简洁易读。
当我们想要阻止一些组件渲染时,让组件中return返回null即可实现,但使用这种方式并不会影响该组件的生命周期方法回调,像componentWillUpdate之类的方法依然可以被调用。

列表&Keys

如果我们想要重复渲染某些组件,手动的一个一个的渲染显然不是明智的做法。这时我们就可选择使用一个循环或是使用map来对需要渲染的对象进行遍历。

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

像这样,我们需要渲染的对象就全部储存在了listItems中。接下来只需把整个listItems插入到ul元素中,然后渲染进DOM:

ReactDOM.render(
  <ul>{listItems}</ul>,
  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')
);

虽然这段代码可以顺利运行,但是我们得到一个警告a key should be provided for list items,它警告我们创建一个元素时,必须包括一个key值,并且这个key值必须是唯一的,这是为什么呢?
React想要识别DOM中那些元素发生了变化就需要可以的辅助才行,所以我们应当给数组中的每个元素都赋予一个唯一的标识符key。

const todoItems = todos.map((todo) =>
  <li key={todo.id}>
    {todo.text}
  </li>
);

表单

受控组件

在HTML中,类似于<input>,<textarea>的表单元素会维持自身的状态,并根据用户的输入进行更新。在React中,这些状态都保存在组件的状态属性中,并且只能用set的方法进行更新。由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>
    );
  }
}

input元素的value属性被我们设置为React数据源上 this.state.value的值,而且每次按键都会触发handleChange来更新当前的state,所以值也会随着用户输入的更新而更新。

状态提升

在我们使用React的时候可能会遇到几个组件需要共用状态数据的情况,这时候我们有一种十分优雅的处理方式,就是将这部分需要共享的状态提升至他们的父组件中,这种方法就是状态提升。让我们来看看具体需要怎么做。
一开始我们先创建一个BoilingVerdict组件,他会接受一个celsius这个表示温度的变量作为他的props属性,然后根据这这个温度来返回一些内容。

function BoilingVerdict(props) {
  if (props.celsius >= 100) {
    return <p>水会烧开</p>;
  }
  return <p>水不会烧开</p>;
}

接着,我们需要写一个带input元素的组件来接受用户的输入,然后将温度的值保存在this.state.temperature中。之后,BoilingVerdict组件就会根据用户输入的温度值来渲染出不同的内容。

class Calculator extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.state = {temperature: ''};
  }

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

  render() {
    const temperature = this.state.temperature;
    return (
      <fieldset>
        <legend>输入一个摄氏温度</legend>
        <input
          value={temperature}
          onChange={this.handleChange} />

        <BoilingVerdict
          celsius={parseFloat(temperature)} />

      </fieldset>
    );
  }
}
添加第二个输入框

现在我们接到了一个新的需求,除了需要可以接受摄氏度的输入,我们还需要多一个输入框,让用户可以输入华氏温度,并且这两个输入框还能同步。
我们从Calculator中抽离出一个TemperatureInput组件出来,为他添加一个表示温度单位的属性。

const scaleNames = {
  c: 'Celsius',
  f: 'Fahrenheit'
};

class TemperatureInput extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.state = {temperature: ''};
  }

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

  render() {
    const temperature = this.state.temperature;
    const scale = this.props.scale;
    return (
      <fieldset>
        <legend>Enter temperature in {scaleNames[scale]}:</legend>
        <input value={temperature}
               onChange={this.handleChange} />
      </fieldset>
    );
  }
}

然后我们就可以根据现有的TemperatureInput组件来渲染出两个不同单位的温度输入框。

class Calculator extends React.Component {
  render() {
    return (
      <div>
        <TemperatureInput scale="c" />
        <TemperatureInput scale="f" />
      </div>
    );
  }
}

虽然现在有了两个输入框,但这并不符合我们的需求,当我们在一个输入框中输入时,另一个输入框并不会跟着一起更新。
而且现在表示温度的数据存在于TemperatureInput组件中,BoilingVerdict也不能渲染出结果。

转换函数

在我们进行状态提升之前我们先要写两个转换函数,能够将摄氏度和华氏度相互转换。

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

function toFahrenheit(celsius) {
  return (celsius * 9 / 5) + 32;
}

光有这个转换函数也并不够,我们还需要一个函数能够接受temperature变量,第二个参数则是我们的转换函数,最后他可以根据一个输入框的值来决定另一个输入框的值。

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();
}
状态提升

接下来就是关键的部分,到现在为止两个TemperatureInput组件都在自己的state中独立保存着数据。但我们想要的是这两个输入框能够进行同步,当在一个输入框输入时可以在另一个输入框中自动转换。这时我们就需要将TemperatureInput组件所保存的state提升到Calculator中。
我们先把TemperatureInput组件中的this.state.temperature 替换为 this.props.temperature,并假设this.props.temperature是已经存在了。

  render() {
    // 之前的代码: const temperature = this.state.temperature;
    const temperature = this.props.temperature;

因为temperature是作为prop从父组件中传递下来的,而我们知道props是只读,所以Temperature组件对这个属性是没有控制权的。
当温度有更新时,就会调用到this.props.onTemperatureChange

  handleChange(e) {
    // 之前的代码: this.setState({temperature: e.target.value});
    this.props.onTemperatureChange(e.target.value);

onTemperatureChangetemperature两个props属性均是从父组件中传递过来的。父组件可以通过自己的反对来响应数据的变化,然后再将值渲染到两个输入框组件,到这里思路就基本清晰了。

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

接下来就是Calculator。我们要定义两个输入框的数据源,能让两个输入框渲染出所需要的数据。

当在摄氏度框中输入37时,Calculator的state就是这样。

{
  temperature: '37',
  scale: '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>
    );
  }
}

此时,我们就完成了两个输入框数据同步的问题。

感悟

React和Vue还有Angular有着很大的不同,几乎所有的内容都是使用JS来完成,并且在React中组件的特征更加明显,想要学好React,一个良好的JS基础是必不可少的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值