is_january

Everything You Should Know About React: The Basics You Need to Start Building

is_january · 2017-11-22推荐 · 11339阅读 CET/4 CET/6 原文链接

Everything You Should Know About React: The Basics You Need to Start Building

Are you curious about React and haven’t had the chance to learn it? Or maybe you’ve tried tutorials in the past, but struggled to master the core concepts? Or maybe you’ve learned the basics, but want to consolidate your knowledge? Either way, this article is for you.

We’re going to build a simple React music player, layering on new React concepts as we go.

Here’s what we’ll cover:

  • What is a React component?

  • ReactDOM rendering

  • Class vs functional components

  • JSX

  • State

  • Event handling

  • Asynchronous setState

  • Props

  • Refs

That’s just about everything you need to build and maintain a React application. But we’re going to introduce it piece-by-piece.

Setup

Here’s the situation: a small start-up has reached out to you for your help. They’ve created a page for users to upload music and have it visualized in glowing colour. But they need you to do the hard part—AKA to make it work.

To get going, make a new project directory and add the following three files.

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>

```

Make sure you’re using an up-to-date version of Chrome with this tutorial, otherwise the animations in the code above won’t work.

Thanks to Steven Fabre for the play button CSS and Justin Windle for visualization code (you can view the original here).

Open up index.html in both a code editor and your browser, and let’s get started!

What is React?

React is a way to build user interfaces. It is only concerned with what you see on the front-end. React makes user interfaces very easy to build by cutting each page into pieces. We call these pieces components.

Here is an example of cutting a page into components:

Each section highlighted above is considered a component. But what does this mean for a developer?

What is a React Component?

A React component is a bit of code that represents a piece of the page. Each component is a JavaScript function that returns a piece of code that represents a piece of a web page.

To build a page, we call these functions in a certain order, put together the result, and show it to the user.

Let’s write a component inside the ` tag inindex.htmlwith thetypeof“text/babel”`:

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

When we call the OurFirstcomponent() function, we will get back a piece of the page.

You can also write functions like this:

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

React uses a language called JSX that looks like HTML but works inside JavaScript, which HTML usually doesn’t do.

You can add plain HTML to this section to make it appear on the 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.

`&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;

Now our ` tag will be put inside the element with the ID ofhook.` It should look like this when you refresh your browser:

We can also write our component in JSX like so:

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

This is standard — invoke your components like you are writing HTML.

Putting Components Together

We can put React components inside other components.

`&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;

This is how we build our page out of pieces of React — by nesting components inside of each other.

Class Components

So far, we’ve been writing components as functions. These are called functional components.

But you can write components another way, as JavaScript classes. These are called class components.

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

Class components must have a function called render(). The render function returns the JSX of the component. They can be used the same way as functional components, like this:``.

You should use functional components over class components because they’re easier to read, unless you need component state (more on that soon).

JavaScript in JSX

You can put JavaScript variables inside of your JSX like this:

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

Now the ‘I am a string!’ will be inside the h1.

You can also do more difficult stuff, like call a function:

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 Gotchas

Rename OurFirstComponent() to PlayButton. We want it to return the following:

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

But there’s a problem: class is a keyword in JavaScript, so we can’t use it. So how do we give our ` a class ofplay`?

Use a property called className instead:

`&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;

What Is This Component Doing?

Class components can store information about their current situation. This information is called state, which is stored in a JavaScript object.

In the code below, we have an object representing our components state. It has a key of isMusicPlaying which has a value of false. This object is assigned to this.state in the constructor method, which is called when the class is first used.

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

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

A constructor method of a React component always needs to call super(props) before anything else.

Okay, so what do we do with state? Why does it exist?

Changing Our React Component Based On State

State is way to update our UI based on events.

In this tutorial, we will use state to change the play button from paused to playing based on the user clicking the play button.

When the user clicks on the button, the state will update, which will then update the UI.

Here’s how we get started. We can look at the component state with this.state. In the following code, we look at the state and use it to decide what text to present to the user.

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

In the render function, this is always referring to the component it is within.

But that’s not very useful unless we have a way to change this.state.isMusicPlaying.

When Stuff Happens to Our Component

The user can interact with our components by clicking on the play button. We want to react (ha… ha…) to those events.

We do that through functions that take care of events. We call these event handlers.

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

When the user clicks on the h1, our component will make the handleClick function run. The function gets the event object as the argument, which means it can use it if it wanted to.

We use the .bind method on handleClick to make sure this refers to the whole component, rather than just the h1.

What This Component Should Be Doing

When we change the state of our component, it will call the render function again.

We can change state with this.setState(), if we give it a new object representing the new state.

Our component on the page will always represent its current state. React does that for us.

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

But clicking an h1 isn’t as good as clicking our actual play button. Let’s make that work.

Talking Between Components

Your components can talk to each other. Let’s try it.

We can tell PlayButton whether or not the music is playing using something called props. Props are information shared from a parent component to a child component.

Props in JSX look the same as HTML properties.

We give PlayButton a prop called isMusicPlaying, which is the same as the isMusicPlaying in this.state.

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

When the state of Container changes, PlayButton prop will change too, and the PlayButton function will be called again. That means our component will update on the screen.

Inside PlayButton, we can react to the change, because PlayButton gets the props as an argument:

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

If we change our state to this.state = { isMusicPlaying: true }; and reload the page, you should see the pause button:

Events as Props

Your props don’t have to be just information. They can be functions.

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

Now, when we click on the PlayButton, it’ll change the state of Container, which will change the props of PlayButton, which will cause it to update on the page.

The Bad Thing About setState

setState is bad because it doesn’t do stuff right away. React waits a bit to see if there are more changes to make, then it does the state changes.

That means you don’t know for sure what your state will be when you call setState.

So you shouldn’t do this:

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

If you are changing your state based on the old state, you need to do things differently.

You need to give setState a function, not an object. This function gets the old state as an argument, and returns an object that is the new state.

It looks like this:

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

It is more difficult, but only needed when you are using the old state to make the new state. If not, you can just give setState an object.

What Are Refs?

Let’s make some music happen.

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

We need a way to get that ` tag and call eitherplay()orpause()on it. We could do it withdocument.getElementById('audio').play()` but there’s a better React way.

We give it a prop called ref, which gets called with the element as the first argument. It takes that element and assigns it to this.audio.

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

This function will be called every time the Container renders, which means this.audio will always be up to date, and equal the `` tag.

We then can play and pause the music:

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

Upload a music file (preferably an mp3 file) using the Choose files button and hit play, and watch it go!

Moving Outside of Index.html

As you might have guessed, our React shouldn’t live forever inside a ``tag.

React takes a lot of build configuration. Fortunately, tools like Create React App take care of all that for you.

Install it to create your own React project. Follow their brief tutorial and start editing the JavaScript inside the src directory, applying all the React knowledge you learned here!

Congratulations!

You can now make React things.

Next, check out a couple of articles for more information. One is about React best practices, the other about a useful part of React called lifecycle methods.

If you learned something from this article, please click those clappin’ hands, and share it with your friends.

You can also follow me on Medium and Twitter.

相关文章