数据API 案例 开发者 关于
掌握聚合最新动态了解行业最新趋势
API接口,开发服务,免费咨询服务
新闻动态 > 媒体报道

关于React你应该了解的一切:开始构建前你所需的基础知识

本文由VicSusi在众成翻译平台翻译。

你是不是对React充满好奇,并且还没有机会学习它?或者你已经尝试过,却挣扎于核心概念的掌握?又或者你已经学习过基础,但是想巩固一下知识?无论你是以上哪种类型,这篇文章都适合你。

我们要基于以下的全新React概念构建一个简单的React音乐播放器。

下面是我们要了解的内容:

  • 什么是React组件?
  • ReactDOM渲染
  • 类 vs 函数化组件
  • JSX
  • 状态
  • 事件操作
  • 异步设置状态
  • 属性
  • 文件系统

以上是构建和维护一个React应用你需要了解的知识。但我们打算按部分来介绍。

设置

现在情况是这样:你要完成一个小小的任务。他们创建了用户上传音乐的页面,并用醒目的颜色让页面更加直观。但他们需要你来完成困难的部分-就是指让这个页面正常工作。

开始前,创建一个新的项目目录,添加以下的三个文件.

app.css


body {

<pre>  background: #f9f9f9;
  font-family: 'Open Sans', sans-serif;
  text-align: center;
}

#container {
  position: relative;
  z-index: 2;
  padding-top: 100px;
}

.play {
  display: block;
  width: 0;
  height: 0;
  border-top: 50px solid transparent;
  border-bottom: 50px solid transparent;
  border-left: 60px solid #2c3e50;
  margin: 100px auto 50px auto;
  position: relative;
  z-index: 1;
  transition: all 0.3s;
  -webkit-transition: all 0.3s;
  -moz-transition: all 0.3s;
  left: 10px;
}

.play:before {
  content: '';
  position: absolute;
  top: -75px;
  left: -115px;
  bottom: -75px;
  right: -35px;
  border-radius: 50%;
  border: 10px solid #2c3e50;
  z-index: 2;
  transition: all 0.3s;
  -webkit-transition: all 0.3s;
  -moz-transition: all 0.3s;
}
.play:after {
  content: '';
  opacity: 0;
  transition: opacity 0.6s;
  -webkit-transition: opacity 0.6s;
  -moz-transition: opacity 0.6s;
}
.play:hover:before, .play:focus:before {
  transform: scale(1.1);
  -webkit-transform: scale(1.1);
  -moz-transform: scale(1.1);
}
.play.active {
  border-color: transparent;
}
.play.active:after {
  content: '';
  opacity: 1;
  width: 25px;
  height: 80px;
  position: absolute;
  right: 8px;
  top: -40px;
  border-right: 20px solid #2c3e50;
  border-left: 20px solid #2c3e50;
}

h1 {
  text-transform: uppercase;
  color: #34495e;
  letter-spacing: 2px;
  font-size: 2em;
  margin-bottom: 0;
}

canvas {
  position: fixed;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
}

audio {
  position: fixed;
  left: 10px;
  bottom: 10px;
  width: calc(100% - 20px);
}
</pre>

<div>```</div>

<div>app.js</div>

<div>```</div>

<div>

<pre>var ALPHA,
  AudioAnalyser,
  COLORS,
  MP3_PATH,
  NUM_BANDS,
  NUM_PARTICLES,
  Particle,
  SCALE,
  SIZE,
  SMOOTHING,
  SPEED,
  SPIN,
  TIMES_CALLED,
  ANALYSER;

NUM_PARTICLES = 150;

NUM_BANDS = 128;

TIMES_CALLED = 0;

SMOOTHING = 0.5;

MP3_PATH = 'music.mp3';

SCALE = {
  MIN: 5.0,
  MAX: 80.0
};

SPEED = {
  MIN: 0.2,
  MAX: 1.0
};

ALPHA = {
  MIN: 0.8,
  MAX: 0.9
};

SPIN = {
  MIN: 0.001,
  MAX: 0.005
};

SIZE = {
  MIN: 0.5,
  MAX: 1.25
};

COLORS = [
  '#69D2E7',
  '#1B676B',
  '#BEF202',
  '#EBE54D',
  '#00CDAC',
  '#1693A5',
  '#F9D423',
  '#FF4E50',
  '#E7204E',
  '#0CCABA',
  '#FF006F'
];
function getAnimation(file) {
  AudioAnalyser = (function() {
    AudioAnalyser.AudioContext = self.AudioContext || self.webkitAudioContext;

    AudioAnalyser.enabled = AudioAnalyser.AudioContext != null;

    function AudioAnalyser(audio, numBands, smoothing) {
      var src;
      this.audio = audio != null ? audio : new Audio();
      this.numBands = numBands != null ? numBands : 256;
      this.smoothing = smoothing != null ? smoothing : 0.3;
      this.audio = document.getElementById('audio');
      if (!this.audio) {
        return;
      }
      try {
        this.audio.src = window.URL.createObjectURL(file);
      } catch (err) {
        console.log(err);
      }
      this.context = new AudioAnalyser.AudioContext();
      this.jsNode = this.context.createScriptProcessor(2048, 1, 1);
      this.analyser = this.context.createAnalyser();
      this.analyser.smoothingTimeConstant = this.smoothing;
      this.analyser.fftSize = this.numBands * 2;
      this.bands = new Uint8Array(this.analyser.frequencyBinCount);
      this.audio.addEventListener(
        'play',
        (function(_this) {
          return function() {
            if (TIMES_CALLED === 1) {
              return;
            }
            ANALYSER.start();
            TIMES_CALLED++;
            _this.source = _this.context.createMediaElementSource(_this.audio);
            _this.source.connect(_this.analyser);
            _this.analyser.connect(_this.jsNode);
            _this.jsNode.connect(_this.context.destination);
            _this.source.connect(_this.context.destination);
            return (_this.jsNode.onaudioprocess = function() {
              _this.analyser.getByteFrequencyData(_this.bands);
              if (!_this.audio.paused) {
                return typeof _this.onUpdate === 'function'
                  ? _this.onUpdate(_this.bands)
                  : void 0;
              }
            });
          };
        })(this)
      );
    }

    AudioAnalyser.prototype.start = function() {
      return this.audio.play();
    };

    AudioAnalyser.prototype.stop = function() {
      return this.audio.pause();
    };

    return AudioAnalyser;
  })();

  Particle = (function() {
    function Particle(x1, y1) {
      this.x = x1 != null ? x1 : 0;
      this.y = y1 != null ? y1 : 0;
      this.reset();
    }

    Particle.prototype.reset = function() {
      this.level = 1 + floor(random(4));
      this.scale = random(SCALE.MIN, SCALE.MAX);
      this.alpha = random(ALPHA.MIN, ALPHA.MAX);
      this.speed = random(SPEED.MIN, SPEED.MAX);
      this.color = random(COLORS);
      this.size = random(SIZE.MIN, SIZE.MAX);
      this.spin = random(SPIN.MAX, SPIN.MAX);
      this.band = floor(random(NUM_BANDS));
      if (random() &lt; 0.5) {
        this.spin = -this.spin;
      }
      this.smoothedScale = 0.0;
      this.smoothedAlpha = 0.0;
      this.decayScale = 0.0;
      this.decayAlpha = 0.0;
      this.rotation = random(TWO_PI);
      return (this.energy = 0.0);
    };

    Particle.prototype.move = function() {
      this.rotation += this.spin;
      return (this.y -= this.speed * this.level);
    };

    Particle.prototype.draw = function(ctx) {
      var alpha, power, scale;
      power = exp(this.energy);
      scale = this.scale * power;
      alpha = this.alpha * this.energy * 1.5;
      this.decayScale = max(this.decayScale, scale);
      this.decayAlpha = max(this.decayAlpha, alpha);
      this.smoothedScale += (this.decayScale - this.smoothedScale) * 0.3;
      this.smoothedAlpha += (this.decayAlpha - this.smoothedAlpha) * 0.3;
      this.decayScale *= 0.985;
      this.decayAlpha *= 0.975;
      ctx.save();
      ctx.beginPath();
      ctx.translate(this.x + cos(this.rotation * this.speed) * 250, this.y);
      ctx.rotate(this.rotation);
      ctx.scale(
        this.smoothedScale * this.level,
        this.smoothedScale * this.level
      );
      ctx.moveTo(this.size * 0.5, 0);
      ctx.lineTo(this.size * -0.5, 0);
      ctx.lineWidth = 1;
      ctx.lineCap = 'round';
      ctx.globalAlpha = this.smoothedAlpha / this.level;
      ctx.strokeStyle = this.color;
      ctx.stroke();
      return ctx.restore();
    };

    return Particle;
  })();

  Sketch.create({
    particles: [],
    setup: function() {
      var analyser, error, i, intro, j, particle, ref, warning, x, y;
      for (i = j = 0, ref = NUM_PARTICLES - 1; j &lt;= ref; i = j += 1) {
        x = random(this.width);
        y = random(this.height * 2);
        particle = new Particle(x, y);
        particle.energy = random(particle.band / 256);
        this.particles.push(particle);
      }
      if (AudioAnalyser.enabled) {
        try {
          analyser = new AudioAnalyser(MP3_PATH, NUM_BANDS, SMOOTHING);
          analyser.onUpdate = (function(_this) {
            return function(bands) {
              var k, len, ref1, results;
              ref1 = _this.particles;
              results = [];
              for (k = 0, len = ref1.length; k &lt; len; k++) {
                particle = ref1[k];
                results.push((particle.energy = bands[particle.band] / 256));
              }
              return results;
            };
          })(this);
          analyser.audio = window.audio;
          ANALYSER = analyser;
          intro = document.getElementById('intro');
          intro.style.display = 'none';
          if (
            /Safari/.test(navigator.userAgent) &&
            !/Chrome/.test(navigator.userAgent)
          ) {
            warning = document.getElementById('warning2');
            return (warning.style.display = 'block');
          }
        } catch (_error) {
          error = _error;
        }
      } else {
        warning = document.getElementById('warning1');
        return (warning.style.display = 'block');
      }
    },
    draw: function() {
      var j, len, particle, ref, results;
      this.globalCompositeOperation = 'lighter';
      ref = this.particles;
      results = [];
      for (j = 0, len = ref.length; j &lt; len; j++) {
        particle = ref[j];
        if (particle.y &lt; -particle.size * particle.level * particle.scale * 2) {
          particle.reset();
          particle.x = random(this.width);
          particle.y =
            this.height + particle.size * particle.scale * particle.level;
        }
        particle.move();
        results.push(particle.draw(this));
      }
      return results;
    }
  });
}

function handleFileSelect(evt) {
  var files = evt.target.files;
  getAnimation(files[0]);
}

getAnimation(null);

document
  .getElementById('files')
  .addEventListener('change', handleFileSelect, false);</pre>

</div>

<div>```</div>

<div>index.html</div>

<div>
<link rel="stylesheet" href="app.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:400,800">

Play Music

<input type="file" id="files" name="files[]" multiple />
`<script crossorigin src="https://unpkg.com/react@15/dist/react.js">`</script> `<script crossorigin src="https://unpkg.com/react-dom@15/dist/react-dom.js">`</script> `<script src="https://npmcdn.com/babel-core@5.8.38/browser.min.js">`</script> `<script src="https://soulwire.github.io/sketch.js/js/sketch.min.js">`</script> `<script src="app.js">`</script> `<script type="text/babel">` // React code goes here. </script>

```

确定你使用了最新版chrome,不然代码中的动画效果无法呈现

谢谢Steven Fabre提供的播放按钮CSS代码,还有Justin Windle提供的可视化代码(你可以在这里查看代码)

在代码编辑器里打开index.html,让我们开始吧!

什么是React?

React是构建用户界面的一种方式。它只和你在前端所见的相关。React通过把每个页面切分为小块让用户界面变得易于构建。我们把这些小块叫做组件。

这里有一个把页面切分为组件的例子:

每个高亮的部分都被视为一个组件。可这对于开发者意味着什么呢?

什么是React组件?

一个React组件就是一个代表页面某部分的代码块。每个组件都是一个JS函数,这个函数返回的代码表示一个web页面的一部分。

为了构建一个页面,我们将按一定顺序调用这些函数,然后把结果放在一起展示给用户。

让我们在index.html中写一个type="text/babel"的组件:

`&lt;script type="text/babel"&gt;`
  function OurFirstComponent() {
    return (
      // Code that represents the UI element goes here
    );
  }
&lt;/script&gt;

当我们调用OurFirstcomponent()函数,我们会得到页面的一个部分。

你可以像下面这样编写函数:

const OurFirstComponent = () =&gt; {
  return (
    // Stuff to make this component goes here
  );
}

React使用了JSX语言,它看起来像HTML但在JS里运行,HTML可不经常这么做。

你可以为这部分添加无样式的HTML,这样就可以在UI上显示了:

`&lt;script type="text/babel"&gt;`
  function OurFirstComponent() {
    return (
      <h1>Hello, I am a React Component!</h1>
    );
  }
&lt;/script&gt;

When we call the OurFirstComponent() function, we get back a bit of JSX. We can use something called ReactDOM to put it on the page.

当我们调用OurFirstComponent函数,我们得到一些JSX代码。我们可以使用ReactDOM把它们放在页面上。

`&lt;script type="text/babel"&gt;`
  function OurFirstComponent() {
    return (
      <h1>Hello, I am a React Component!</h1>
    );
  }
 **const placeWeWantToPutComponent = document.getElementById('hook');
  ReactDOM.render(OurFirstComponent(), placeWeWantToPutComponent);**
&lt;/script&gt;

现在通过ID我们会将标签放入元素中。当你刷新浏览器时它看起来应该是这样:

我们可以像这样用JSX编写组件:

ReactDOM.render(&lt;OurFirstComponent /&gt;, placeWeWantToPutComponent);

这是标准-调用组件就像编写HTML,效果是一样的。

把组件放在一起

我们可以把React组件放在其他组件里。

`&lt;script type="text/babel"&gt;`
  function OurFirstComponent() {
    return (
      **<h1>I am the child!</h1>**
    );
  }
 **function Container() {
    return (
      <div>
        <h1>I am the parent!</h1>
        &lt;OurFirstComponent /&gt;
      </div>
    );
  }**
  const placeWeWantToPutComponent = document.getElementById('hook');
  ReactDOM.render(**&lt;Container /&gt;**, placeWeWantToPutComponent);
&lt;/script&gt;

我们就是这样通过React组件构造页面的-通过组件嵌套组件实现。

类组件

到目前为止,我们一直在像编写函数一样编写组件。它们被称为函数式组件.

但你也可以用另一种方式编写组件,比如JavaScript类。这样编写的组件被称作类组件。

class Container extends React.Component {
  render() {
    return (
      <div>
        <h1>I am the parent!</h1>
        &lt;OurFirstComponent /&gt;
      </div>
    );
  }
}
const placeWeWantToPutComponent = document.getElementById('hook');
ReactDOM.render(&lt;Container /&gt;, placeWeWantToPutComponent);

类组件一定要有一个render()函数.这个渲染函数返回组件的JSX代码。它们和函数化组件的使用方法相同。

你应该更多地使用函数化组件而不是类组件,因为前者更容易阅读,除非你需要组件状态(稍后介绍)

JSX中的JavaScript

你可以把JavaScript变量这样放进你的JSX中:

class Container extends React.Component {
  render() {
    **const greeting = 'I am a string!';**
    return (
      <div>
        **<h1>{ greeting }</h1>**
        &lt;OurFirstComponent /&gt;
      </div>
    );
  }
}

现在I am a string会在h1标签内出现。

你也可以做点更难的,比如调用一个函数:

class Container extends React.Component {
  render() {
    **const addNumbers = (num1, num2) =&gt; {
      return num1 + num2;
    };**
    return (
      <div>
        **<h1>The sum is: { addNumbers(1, 2) }</h1>**
        &lt;OurFirstComponent /&gt;
      </div>
    );
  }
}

JSX可伸缩性

将OurFirstComponent()更名为PlayButton。我们想要它返回下面的内容:

<a href="#" title="Play video" />

可现在有个问题:class在JavaScript中是关键字,所以我们无法使用它。那我们该如何创建一个示例?

用一个名字叫className的属性代替:

`&lt;script type="text/babel"&gt;`
 **function PlayButton() {
    return <a href="#" title="Play video" />;
  }**
  class Container extends React.Component {
    render() {
      return (
        <div>
          **&lt;PlayButton /&gt;**
        </div>
      );
    }
  }
  const placeWeWantToPutComponent = document.getElementById('hook');
  ReactDOM.render(&lt;Container /&gt;, placeWeWantToPutComponent);
&lt;/script&gt;

这个组件在干什么?

类组件可以存储关于它们最近状态的信息。这个信息被称为状态,储存在一个JavaScript对象中。

下面的代码里,有一个对象代表我们组件的状态。对象有一个键名叫isMusicPlaying,值为false。这个对象被以建造者模式分配给this.state,当这个类首次使用时this.state被调用。

class Container extends React.Component {
  **constructor(props) {
    super(props);
    this.state = { isMusicPlaying: false };
  }**

  render() {
    return (
      <div>
        &lt;PlayButton /&gt;
      </div>
    );
  }
}

一个React组件的建造者模式总是需要在最开始就调用super(props)。

好,那我们要状态干什么呢?为什么它会存在?

基于状态改变我们的React组件

可以基于事件的状态来更新我们的UI。

在本文中,我们会基于用户点击播放按钮的操作,利用状态实现播放按钮从停止到播放的改变。

当用户点击按钮,状态就会更新,然后UI就会更新。

我们按下面的步骤开始。我们可以利用this,state监视组件状态。下面的代码中,我们持续监视状态并根据它来决定展示给用户什么样的内容。

class Container extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isMusicPlaying: false };
  }
  render() {
    **const status = this.state.isMusicPlaying ? 'Playing' : 'Not playing';**
    return (
      <div>
        **<h1>{ status }</h1>**
        &lt;PlayButton /&gt;
      </div>
    );
  }
}

在这个渲染函数中,this经常表示它所在的组件。

但这并没有什么用处,除非我们有方法可以改变this.state.isMusicPlaying。

当我们的组件发生变化的时候

用户可以通过点击播放按钮与我们的组件实现交互。我们想对这些事件做出反应(ha...ha...)。

我们通过与事件相关的函数实现交互操作。我们称之为事件句柄。

class Container extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isMusicPlaying: false };
  }
 **handleClick(event) {
    // Do something about the click
  };**
  render() {
    let status = this.state.isMusicPlaying 
    ? 'Playing :)' 
    : 'Not playing :(';
    return (
      <div>
        <h1>{ status }</h1>
        &lt;PlayButton /&gt;
      </div>
    );
  }
}

当用户点击h1时,我们的组件会运行handleClick函数。这个函数将得到的时间对象作为参数,这意味着它可以随心所欲地使用该对象。

我们在handleClick上使用.bind方法,以确保this特指整个组件,而不只是h1。

那这个组件应该干什么

当我们改变了组件的状态,它会再次调用渲染函数。

我们可以随this.setState()改变状态,只要我们给它一个表示新状态的新对象。

我们页面中的组件会始终展示它们的最新状态。这是React替我们做的。

handleClick() {
    **if (this.state.isMusicPlaying) {
      this.setState({ isMusicPlaying: false });
    } else {
      this.setState({ isMusicPlaying: true });
    }**
  };

可是点击一个h1还是不如点击播放按钮方便。让我们动手实现一下吧。

组件间的通信

你的组件可以互相通信。让我们试一下。

我们可以用属性告知播放按钮音乐是否在播放。属性是一个父组件分享给子组件的信息。

JSX中的属性看起来和HTML属性差不多。

我们给播放按钮设置一个名为isMusicPlaying的属性,它和this.state里的isMusicPlaying一样。

class Container extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isMusicPlaying: false };
  }
  handleClick() {
    if (this.state.isMusicPlaying) {
      this.setState({ isMusicPlaying: false });
    } else {
      this.setState({ isMusicPlaying: true });
    }
  };
  render() {
    return (
      <div>
 **&lt;PlayButton isMusicPlaying={this.state.isMusicPlaying} /&gt;**      </div>
    );
  }
}

当Container的状态改变,PlayButton属性也会改变,PlayButton函数也会被再次调用。这意味着我们的组件会在屏幕上更新。

在PlayButton里,我们可以对改变做出反应,因为PlayButton会将得到的属性作为参数。

function PlayButton(**props**) {
 **const className = props.isMusicPlaying ? 'play active' : 'play';**  return <a href="#" title="Play video" />;
}

如果我们改变状态为this.state = {isMusicPlaying:true };,然后刷新页面,你应该能看到暂停按钮。

基于属性的事件

你的属性没有必要必须为信息。它们可以是函数。

function PlayButton(props) {
  const className = props.isMusicPlaying ? 'play active' : 'play';
  return <a href="#" title="Play video" />;
}
class Container extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isMusicPlaying: false };
  }
  handleClick() {
    if (this.state.isMusicPlaying) {
      this.setState({ isMusicPlaying: false });
    } else {
      this.setState({ isMusicPlaying: true });
    }
  };
  render() {
    return (
      <div>
        &lt;PlayButton 
          **onClick={this.handleClick.bind(this)}** 
          isMusicPlaying={this.state.isMusicPlaying} 
        /&gt;
      </div>
    );
  }
}

现在,当我们点击播放按钮,Container的状态会改变,这会改变播放按钮的属性,导致按钮在页面上刷新。

设置状态的坏处

setState很糟糕,因为它不会立即响应。React会等待一会以便查看是否有更多改变要做,之后才会改变状态。

这意味着你不确定当你调用setState时你的状态会变成什么。

所以你不应该这样做:

handleClick() {
  this.setState({ isMusicPlaying: !this.state.isMusicPlaying });
};

如果你是基于旧状态来改变新状态,你需要做点不一样的工作。

你需要赋给getState一个函数而不是对象。这个函数获得旧状态作为一个参数,返回一个新对象,这个对象即为新状态。

它看起来像是这样:

handleClick() {
  this.setState(prevState =&gt; {
    return { 
      isMusicPlaying: !prevState.isMusicPlaying   
    };
  });
};

这更困难,但只有当你使用旧状态来获得新状态时才需要。如果不是,你可以只赋给setState一个对象。

什么是Refs?

让我们试试播放音乐。

First, we add an `` tag: 首先,我们添加一个标签:

class Container extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isMusicPlaying: false };
  }
  handleClick() {
    this.setState(prevState =&gt; {
      return { 
        isMusicPlaying: !prevState.isMusicPlaying   
      };
    });
  };
  render() {
    return (
      <div>
        &lt;PlayButton 
          onClick={this.handleClick.bind(this)} 
          isMusicPlaying={this.state.isMusicPlaying} 
        /&gt;
 **<audio />**      </div>
    );
  }
}

我们需要一种方法来获得标签,然后在该标签上调用play()或者pause()。我们可以用document.getElementById('audio').play()实现,但还有更好的React方法。

我们赋给它一个属性ref,该属性和被作为第一个参数的元素一起被调用。它获得这个元素并把元素分配给this.audio。

<audio> { this.audio = audioTag }}** /&gt;

Container每次进行渲染时都会调用这个函数,这意味着this.audio会始终保持更新。

我们这时可以播放和暂停音乐:

handleClick() {
  **if (this.state.isMusicPlaying) {
    this.audio.pause();
  } else {
    this.audio.play();
  }**
  this.setState(prevState =&gt; {
    return { 
      isMusicPlaying: !prevState.isMusicPlaying   
    };
  });
};

使用选择文件按钮上传一个音乐文件(mp3文件更合适),然后点击播放就能欣赏音乐啦。

跳出index.html

和你想的一样,我们的React不应该围着标签转。

React需要很多构建配置。幸运的是,一些工具比如Create React App为你做好了一切。

安装该工具来创建你自己的React项目。跟着它们简洁的文档,开始编辑src目录里的JavaScript代码,把你所学到的React知识应用在这吧。

祝贺

现在你可以用React做一些东西了。

接下来,阅读其他文章来获得更多信息。一篇是React best practices,另一篇是关于React一个有用的部分lifecycle methods.

如果你从本篇文章中学到了一点东西,请为我们点赞,并分享给你的朋友们。

你也可以关注我的MediumTwitter

原文来自:众成翻译

掌握聚合最新动态了解行业最新趋势
API接口,开发服务,免费咨询服务
新闻动态 > 媒体报道
关于React你应该了解的一切:开始构建前你所需的基础知识
发布:2017-12-18

本文由VicSusi在众成翻译平台翻译。

你是不是对React充满好奇,并且还没有机会学习它?或者你已经尝试过,却挣扎于核心概念的掌握?又或者你已经学习过基础,但是想巩固一下知识?无论你是以上哪种类型,这篇文章都适合你。

我们要基于以下的全新React概念构建一个简单的React音乐播放器。

下面是我们要了解的内容:

  • 什么是React组件?
  • ReactDOM渲染
  • 类 vs 函数化组件
  • JSX
  • 状态
  • 事件操作
  • 异步设置状态
  • 属性
  • 文件系统

以上是构建和维护一个React应用你需要了解的知识。但我们打算按部分来介绍。

设置

现在情况是这样:你要完成一个小小的任务。他们创建了用户上传音乐的页面,并用醒目的颜色让页面更加直观。但他们需要你来完成困难的部分-就是指让这个页面正常工作。

开始前,创建一个新的项目目录,添加以下的三个文件.

app.css


body {

<pre>  background: #f9f9f9;
  font-family: 'Open Sans', sans-serif;
  text-align: center;
}

#container {
  position: relative;
  z-index: 2;
  padding-top: 100px;
}

.play {
  display: block;
  width: 0;
  height: 0;
  border-top: 50px solid transparent;
  border-bottom: 50px solid transparent;
  border-left: 60px solid #2c3e50;
  margin: 100px auto 50px auto;
  position: relative;
  z-index: 1;
  transition: all 0.3s;
  -webkit-transition: all 0.3s;
  -moz-transition: all 0.3s;
  left: 10px;
}

.play:before {
  content: '';
  position: absolute;
  top: -75px;
  left: -115px;
  bottom: -75px;
  right: -35px;
  border-radius: 50%;
  border: 10px solid #2c3e50;
  z-index: 2;
  transition: all 0.3s;
  -webkit-transition: all 0.3s;
  -moz-transition: all 0.3s;
}
.play:after {
  content: '';
  opacity: 0;
  transition: opacity 0.6s;
  -webkit-transition: opacity 0.6s;
  -moz-transition: opacity 0.6s;
}
.play:hover:before, .play:focus:before {
  transform: scale(1.1);
  -webkit-transform: scale(1.1);
  -moz-transform: scale(1.1);
}
.play.active {
  border-color: transparent;
}
.play.active:after {
  content: '';
  opacity: 1;
  width: 25px;
  height: 80px;
  position: absolute;
  right: 8px;
  top: -40px;
  border-right: 20px solid #2c3e50;
  border-left: 20px solid #2c3e50;
}

h1 {
  text-transform: uppercase;
  color: #34495e;
  letter-spacing: 2px;
  font-size: 2em;
  margin-bottom: 0;
}

canvas {
  position: fixed;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
}

audio {
  position: fixed;
  left: 10px;
  bottom: 10px;
  width: calc(100% - 20px);
}
</pre>

<div>```</div>

<div>app.js</div>

<div>```</div>

<div>

<pre>var ALPHA,
  AudioAnalyser,
  COLORS,
  MP3_PATH,
  NUM_BANDS,
  NUM_PARTICLES,
  Particle,
  SCALE,
  SIZE,
  SMOOTHING,
  SPEED,
  SPIN,
  TIMES_CALLED,
  ANALYSER;

NUM_PARTICLES = 150;

NUM_BANDS = 128;

TIMES_CALLED = 0;

SMOOTHING = 0.5;

MP3_PATH = 'music.mp3';

SCALE = {
  MIN: 5.0,
  MAX: 80.0
};

SPEED = {
  MIN: 0.2,
  MAX: 1.0
};

ALPHA = {
  MIN: 0.8,
  MAX: 0.9
};

SPIN = {
  MIN: 0.001,
  MAX: 0.005
};

SIZE = {
  MIN: 0.5,
  MAX: 1.25
};

COLORS = [
  '#69D2E7',
  '#1B676B',
  '#BEF202',
  '#EBE54D',
  '#00CDAC',
  '#1693A5',
  '#F9D423',
  '#FF4E50',
  '#E7204E',
  '#0CCABA',
  '#FF006F'
];
function getAnimation(file) {
  AudioAnalyser = (function() {
    AudioAnalyser.AudioContext = self.AudioContext || self.webkitAudioContext;

    AudioAnalyser.enabled = AudioAnalyser.AudioContext != null;

    function AudioAnalyser(audio, numBands, smoothing) {
      var src;
      this.audio = audio != null ? audio : new Audio();
      this.numBands = numBands != null ? numBands : 256;
      this.smoothing = smoothing != null ? smoothing : 0.3;
      this.audio = document.getElementById('audio');
      if (!this.audio) {
        return;
      }
      try {
        this.audio.src = window.URL.createObjectURL(file);
      } catch (err) {
        console.log(err);
      }
      this.context = new AudioAnalyser.AudioContext();
      this.jsNode = this.context.createScriptProcessor(2048, 1, 1);
      this.analyser = this.context.createAnalyser();
      this.analyser.smoothingTimeConstant = this.smoothing;
      this.analyser.fftSize = this.numBands * 2;
      this.bands = new Uint8Array(this.analyser.frequencyBinCount);
      this.audio.addEventListener(
        'play',
        (function(_this) {
          return function() {
            if (TIMES_CALLED === 1) {
              return;
            }
            ANALYSER.start();
            TIMES_CALLED++;
            _this.source = _this.context.createMediaElementSource(_this.audio);
            _this.source.connect(_this.analyser);
            _this.analyser.connect(_this.jsNode);
            _this.jsNode.connect(_this.context.destination);
            _this.source.connect(_this.context.destination);
            return (_this.jsNode.onaudioprocess = function() {
              _this.analyser.getByteFrequencyData(_this.bands);
              if (!_this.audio.paused) {
                return typeof _this.onUpdate === 'function'
                  ? _this.onUpdate(_this.bands)
                  : void 0;
              }
            });
          };
        })(this)
      );
    }

    AudioAnalyser.prototype.start = function() {
      return this.audio.play();
    };

    AudioAnalyser.prototype.stop = function() {
      return this.audio.pause();
    };

    return AudioAnalyser;
  })();

  Particle = (function() {
    function Particle(x1, y1) {
      this.x = x1 != null ? x1 : 0;
      this.y = y1 != null ? y1 : 0;
      this.reset();
    }

    Particle.prototype.reset = function() {
      this.level = 1 + floor(random(4));
      this.scale = random(SCALE.MIN, SCALE.MAX);
      this.alpha = random(ALPHA.MIN, ALPHA.MAX);
      this.speed = random(SPEED.MIN, SPEED.MAX);
      this.color = random(COLORS);
      this.size = random(SIZE.MIN, SIZE.MAX);
      this.spin = random(SPIN.MAX, SPIN.MAX);
      this.band = floor(random(NUM_BANDS));
      if (random() &lt; 0.5) {
        this.spin = -this.spin;
      }
      this.smoothedScale = 0.0;
      this.smoothedAlpha = 0.0;
      this.decayScale = 0.0;
      this.decayAlpha = 0.0;
      this.rotation = random(TWO_PI);
      return (this.energy = 0.0);
    };

    Particle.prototype.move = function() {
      this.rotation += this.spin;
      return (this.y -= this.speed * this.level);
    };

    Particle.prototype.draw = function(ctx) {
      var alpha, power, scale;
      power = exp(this.energy);
      scale = this.scale * power;
      alpha = this.alpha * this.energy * 1.5;
      this.decayScale = max(this.decayScale, scale);
      this.decayAlpha = max(this.decayAlpha, alpha);
      this.smoothedScale += (this.decayScale - this.smoothedScale) * 0.3;
      this.smoothedAlpha += (this.decayAlpha - this.smoothedAlpha) * 0.3;
      this.decayScale *= 0.985;
      this.decayAlpha *= 0.975;
      ctx.save();
      ctx.beginPath();
      ctx.translate(this.x + cos(this.rotation * this.speed) * 250, this.y);
      ctx.rotate(this.rotation);
      ctx.scale(
        this.smoothedScale * this.level,
        this.smoothedScale * this.level
      );
      ctx.moveTo(this.size * 0.5, 0);
      ctx.lineTo(this.size * -0.5, 0);
      ctx.lineWidth = 1;
      ctx.lineCap = 'round';
      ctx.globalAlpha = this.smoothedAlpha / this.level;
      ctx.strokeStyle = this.color;
      ctx.stroke();
      return ctx.restore();
    };

    return Particle;
  })();

  Sketch.create({
    particles: [],
    setup: function() {
      var analyser, error, i, intro, j, particle, ref, warning, x, y;
      for (i = j = 0, ref = NUM_PARTICLES - 1; j &lt;= ref; i = j += 1) {
        x = random(this.width);
        y = random(this.height * 2);
        particle = new Particle(x, y);
        particle.energy = random(particle.band / 256);
        this.particles.push(particle);
      }
      if (AudioAnalyser.enabled) {
        try {
          analyser = new AudioAnalyser(MP3_PATH, NUM_BANDS, SMOOTHING);
          analyser.onUpdate = (function(_this) {
            return function(bands) {
              var k, len, ref1, results;
              ref1 = _this.particles;
              results = [];
              for (k = 0, len = ref1.length; k &lt; len; k++) {
                particle = ref1[k];
                results.push((particle.energy = bands[particle.band] / 256));
              }
              return results;
            };
          })(this);
          analyser.audio = window.audio;
          ANALYSER = analyser;
          intro = document.getElementById('intro');
          intro.style.display = 'none';
          if (
            /Safari/.test(navigator.userAgent) &&
            !/Chrome/.test(navigator.userAgent)
          ) {
            warning = document.getElementById('warning2');
            return (warning.style.display = 'block');
          }
        } catch (_error) {
          error = _error;
        }
      } else {
        warning = document.getElementById('warning1');
        return (warning.style.display = 'block');
      }
    },
    draw: function() {
      var j, len, particle, ref, results;
      this.globalCompositeOperation = 'lighter';
      ref = this.particles;
      results = [];
      for (j = 0, len = ref.length; j &lt; len; j++) {
        particle = ref[j];
        if (particle.y &lt; -particle.size * particle.level * particle.scale * 2) {
          particle.reset();
          particle.x = random(this.width);
          particle.y =
            this.height + particle.size * particle.scale * particle.level;
        }
        particle.move();
        results.push(particle.draw(this));
      }
      return results;
    }
  });
}

function handleFileSelect(evt) {
  var files = evt.target.files;
  getAnimation(files[0]);
}

getAnimation(null);

document
  .getElementById('files')
  .addEventListener('change', handleFileSelect, false);</pre>

</div>

<div>```</div>

<div>index.html</div>

<div>
<link rel="stylesheet" href="app.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:400,800">

Play Music

<input type="file" id="files" name="files[]" multiple />
`<script crossorigin src="https://unpkg.com/react@15/dist/react.js">`</script> `<script crossorigin src="https://unpkg.com/react-dom@15/dist/react-dom.js">`</script> `<script src="https://npmcdn.com/babel-core@5.8.38/browser.min.js">`</script> `<script src="https://soulwire.github.io/sketch.js/js/sketch.min.js">`</script> `<script src="app.js">`</script> `<script type="text/babel">` // React code goes here. </script>

```

确定你使用了最新版chrome,不然代码中的动画效果无法呈现

谢谢Steven Fabre提供的播放按钮CSS代码,还有Justin Windle提供的可视化代码(你可以在这里查看代码)

在代码编辑器里打开index.html,让我们开始吧!

什么是React?

React是构建用户界面的一种方式。它只和你在前端所见的相关。React通过把每个页面切分为小块让用户界面变得易于构建。我们把这些小块叫做组件。

这里有一个把页面切分为组件的例子:

每个高亮的部分都被视为一个组件。可这对于开发者意味着什么呢?

什么是React组件?

一个React组件就是一个代表页面某部分的代码块。每个组件都是一个JS函数,这个函数返回的代码表示一个web页面的一部分。

为了构建一个页面,我们将按一定顺序调用这些函数,然后把结果放在一起展示给用户。

让我们在index.html中写一个type="text/babel"的组件:

`&lt;script type="text/babel"&gt;`
  function OurFirstComponent() {
    return (
      // Code that represents the UI element goes here
    );
  }
&lt;/script&gt;

当我们调用OurFirstcomponent()函数,我们会得到页面的一个部分。

你可以像下面这样编写函数:

const OurFirstComponent = () =&gt; {
  return (
    // Stuff to make this component goes here
  );
}

React使用了JSX语言,它看起来像HTML但在JS里运行,HTML可不经常这么做。

你可以为这部分添加无样式的HTML,这样就可以在UI上显示了:

`&lt;script type="text/babel"&gt;`
  function OurFirstComponent() {
    return (
      <h1>Hello, I am a React Component!</h1>
    );
  }
&lt;/script&gt;

When we call the OurFirstComponent() function, we get back a bit of JSX. We can use something called ReactDOM to put it on the page.

当我们调用OurFirstComponent函数,我们得到一些JSX代码。我们可以使用ReactDOM把它们放在页面上。

`&lt;script type="text/babel"&gt;`
  function OurFirstComponent() {
    return (
      <h1>Hello, I am a React Component!</h1>
    );
  }
 **const placeWeWantToPutComponent = document.getElementById('hook');
  ReactDOM.render(OurFirstComponent(), placeWeWantToPutComponent);**
&lt;/script&gt;

现在通过ID我们会将标签放入元素中。当你刷新浏览器时它看起来应该是这样:

我们可以像这样用JSX编写组件:

ReactDOM.render(&lt;OurFirstComponent /&gt;, placeWeWantToPutComponent);

这是标准-调用组件就像编写HTML,效果是一样的。

把组件放在一起

我们可以把React组件放在其他组件里。

`&lt;script type="text/babel"&gt;`
  function OurFirstComponent() {
    return (
      **<h1>I am the child!</h1>**
    );
  }
 **function Container() {
    return (
      <div>
        <h1>I am the parent!</h1>
        &lt;OurFirstComponent /&gt;
      </div>
    );
  }**
  const placeWeWantToPutComponent = document.getElementById('hook');
  ReactDOM.render(**&lt;Container /&gt;**, placeWeWantToPutComponent);
&lt;/script&gt;

我们就是这样通过React组件构造页面的-通过组件嵌套组件实现。

类组件

到目前为止,我们一直在像编写函数一样编写组件。它们被称为函数式组件.

但你也可以用另一种方式编写组件,比如JavaScript类。这样编写的组件被称作类组件。

class Container extends React.Component {
  render() {
    return (
      <div>
        <h1>I am the parent!</h1>
        &lt;OurFirstComponent /&gt;
      </div>
    );
  }
}
const placeWeWantToPutComponent = document.getElementById('hook');
ReactDOM.render(&lt;Container /&gt;, placeWeWantToPutComponent);

类组件一定要有一个render()函数.这个渲染函数返回组件的JSX代码。它们和函数化组件的使用方法相同。

你应该更多地使用函数化组件而不是类组件,因为前者更容易阅读,除非你需要组件状态(稍后介绍)

JSX中的JavaScript

你可以把JavaScript变量这样放进你的JSX中:

class Container extends React.Component {
  render() {
    **const greeting = 'I am a string!';**
    return (
      <div>
        **<h1>{ greeting }</h1>**
        &lt;OurFirstComponent /&gt;
      </div>
    );
  }
}

现在I am a string会在h1标签内出现。

你也可以做点更难的,比如调用一个函数:

class Container extends React.Component {
  render() {
    **const addNumbers = (num1, num2) =&gt; {
      return num1 + num2;
    };**
    return (
      <div>
        **<h1>The sum is: { addNumbers(1, 2) }</h1>**
        &lt;OurFirstComponent /&gt;
      </div>
    );
  }
}

JSX可伸缩性

将OurFirstComponent()更名为PlayButton。我们想要它返回下面的内容:

<a href="#" title="Play video" />

可现在有个问题:class在JavaScript中是关键字,所以我们无法使用它。那我们该如何创建一个示例?

用一个名字叫className的属性代替:

`&lt;script type="text/babel"&gt;`
 **function PlayButton() {
    return <a href="#" title="Play video" />;
  }**
  class Container extends React.Component {
    render() {
      return (
        <div>
          **&lt;PlayButton /&gt;**
        </div>
      );
    }
  }
  const placeWeWantToPutComponent = document.getElementById('hook');
  ReactDOM.render(&lt;Container /&gt;, placeWeWantToPutComponent);
&lt;/script&gt;

这个组件在干什么?

类组件可以存储关于它们最近状态的信息。这个信息被称为状态,储存在一个JavaScript对象中。

下面的代码里,有一个对象代表我们组件的状态。对象有一个键名叫isMusicPlaying,值为false。这个对象被以建造者模式分配给this.state,当这个类首次使用时this.state被调用。

class Container extends React.Component {
  **constructor(props) {
    super(props);
    this.state = { isMusicPlaying: false };
  }**

  render() {
    return (
      <div>
        &lt;PlayButton /&gt;
      </div>
    );
  }
}

一个React组件的建造者模式总是需要在最开始就调用super(props)。

好,那我们要状态干什么呢?为什么它会存在?

基于状态改变我们的React组件

可以基于事件的状态来更新我们的UI。

在本文中,我们会基于用户点击播放按钮的操作,利用状态实现播放按钮从停止到播放的改变。

当用户点击按钮,状态就会更新,然后UI就会更新。

我们按下面的步骤开始。我们可以利用this,state监视组件状态。下面的代码中,我们持续监视状态并根据它来决定展示给用户什么样的内容。

class Container extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isMusicPlaying: false };
  }
  render() {
    **const status = this.state.isMusicPlaying ? 'Playing' : 'Not playing';**
    return (
      <div>
        **<h1>{ status }</h1>**
        &lt;PlayButton /&gt;
      </div>
    );
  }
}

在这个渲染函数中,this经常表示它所在的组件。

但这并没有什么用处,除非我们有方法可以改变this.state.isMusicPlaying。

当我们的组件发生变化的时候

用户可以通过点击播放按钮与我们的组件实现交互。我们想对这些事件做出反应(ha...ha...)。

我们通过与事件相关的函数实现交互操作。我们称之为事件句柄。

class Container extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isMusicPlaying: false };
  }
 **handleClick(event) {
    // Do something about the click
  };**
  render() {
    let status = this.state.isMusicPlaying 
    ? 'Playing :)' 
    : 'Not playing :(';
    return (
      <div>
        <h1>{ status }</h1>
        &lt;PlayButton /&gt;
      </div>
    );
  }
}

当用户点击h1时,我们的组件会运行handleClick函数。这个函数将得到的时间对象作为参数,这意味着它可以随心所欲地使用该对象。

我们在handleClick上使用.bind方法,以确保this特指整个组件,而不只是h1。

那这个组件应该干什么

当我们改变了组件的状态,它会再次调用渲染函数。

我们可以随this.setState()改变状态,只要我们给它一个表示新状态的新对象。

我们页面中的组件会始终展示它们的最新状态。这是React替我们做的。

handleClick() {
    **if (this.state.isMusicPlaying) {
      this.setState({ isMusicPlaying: false });
    } else {
      this.setState({ isMusicPlaying: true });
    }**
  };

可是点击一个h1还是不如点击播放按钮方便。让我们动手实现一下吧。

组件间的通信

你的组件可以互相通信。让我们试一下。

我们可以用属性告知播放按钮音乐是否在播放。属性是一个父组件分享给子组件的信息。

JSX中的属性看起来和HTML属性差不多。

我们给播放按钮设置一个名为isMusicPlaying的属性,它和this.state里的isMusicPlaying一样。

class Container extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isMusicPlaying: false };
  }
  handleClick() {
    if (this.state.isMusicPlaying) {
      this.setState({ isMusicPlaying: false });
    } else {
      this.setState({ isMusicPlaying: true });
    }
  };
  render() {
    return (
      <div>
 **&lt;PlayButton isMusicPlaying={this.state.isMusicPlaying} /&gt;**      </div>
    );
  }
}

当Container的状态改变,PlayButton属性也会改变,PlayButton函数也会被再次调用。这意味着我们的组件会在屏幕上更新。

在PlayButton里,我们可以对改变做出反应,因为PlayButton会将得到的属性作为参数。

function PlayButton(**props**) {
 **const className = props.isMusicPlaying ? 'play active' : 'play';**  return <a href="#" title="Play video" />;
}

如果我们改变状态为this.state = {isMusicPlaying:true };,然后刷新页面,你应该能看到暂停按钮。

基于属性的事件

你的属性没有必要必须为信息。它们可以是函数。

function PlayButton(props) {
  const className = props.isMusicPlaying ? 'play active' : 'play';
  return <a href="#" title="Play video" />;
}
class Container extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isMusicPlaying: false };
  }
  handleClick() {
    if (this.state.isMusicPlaying) {
      this.setState({ isMusicPlaying: false });
    } else {
      this.setState({ isMusicPlaying: true });
    }
  };
  render() {
    return (
      <div>
        &lt;PlayButton 
          **onClick={this.handleClick.bind(this)}** 
          isMusicPlaying={this.state.isMusicPlaying} 
        /&gt;
      </div>
    );
  }
}

现在,当我们点击播放按钮,Container的状态会改变,这会改变播放按钮的属性,导致按钮在页面上刷新。

设置状态的坏处

setState很糟糕,因为它不会立即响应。React会等待一会以便查看是否有更多改变要做,之后才会改变状态。

这意味着你不确定当你调用setState时你的状态会变成什么。

所以你不应该这样做:

handleClick() {
  this.setState({ isMusicPlaying: !this.state.isMusicPlaying });
};

如果你是基于旧状态来改变新状态,你需要做点不一样的工作。

你需要赋给getState一个函数而不是对象。这个函数获得旧状态作为一个参数,返回一个新对象,这个对象即为新状态。

它看起来像是这样:

handleClick() {
  this.setState(prevState =&gt; {
    return { 
      isMusicPlaying: !prevState.isMusicPlaying   
    };
  });
};

这更困难,但只有当你使用旧状态来获得新状态时才需要。如果不是,你可以只赋给setState一个对象。

什么是Refs?

让我们试试播放音乐。

First, we add an `` tag: 首先,我们添加一个标签:

class Container extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isMusicPlaying: false };
  }
  handleClick() {
    this.setState(prevState =&gt; {
      return { 
        isMusicPlaying: !prevState.isMusicPlaying   
      };
    });
  };
  render() {
    return (
      <div>
        &lt;PlayButton 
          onClick={this.handleClick.bind(this)} 
          isMusicPlaying={this.state.isMusicPlaying} 
        /&gt;
 **<audio />**      </div>
    );
  }
}

我们需要一种方法来获得标签,然后在该标签上调用play()或者pause()。我们可以用document.getElementById('audio').play()实现,但还有更好的React方法。

我们赋给它一个属性ref,该属性和被作为第一个参数的元素一起被调用。它获得这个元素并把元素分配给this.audio。

<audio> { this.audio = audioTag }}** /&gt;

Container每次进行渲染时都会调用这个函数,这意味着this.audio会始终保持更新。

我们这时可以播放和暂停音乐:

handleClick() {
  **if (this.state.isMusicPlaying) {
    this.audio.pause();
  } else {
    this.audio.play();
  }**
  this.setState(prevState =&gt; {
    return { 
      isMusicPlaying: !prevState.isMusicPlaying   
    };
  });
};

使用选择文件按钮上传一个音乐文件(mp3文件更合适),然后点击播放就能欣赏音乐啦。

跳出index.html

和你想的一样,我们的React不应该围着标签转。

React需要很多构建配置。幸运的是,一些工具比如Create React App为你做好了一切。

安装该工具来创建你自己的React项目。跟着它们简洁的文档,开始编辑src目录里的JavaScript代码,把你所学到的React知识应用在这吧。

祝贺

现在你可以用React做一些东西了。

接下来,阅读其他文章来获得更多信息。一篇是React best practices,另一篇是关于React一个有用的部分lifecycle methods.

如果你从本篇文章中学到了一点东西,请为我们点赞,并分享给你的朋友们。

你也可以关注我的MediumTwitter

原文来自:众成翻译

电话 0512-88869195
数 据 驱 动 未 来
Data Drives The Future