mirror of
https://github.com/microsoft/frontend-bootcamp.git
synced 2026-01-26 14:56:42 +08:00
minor readme tweaks
This commit is contained in:
@@ -43,7 +43,7 @@
|
||||
<div class="Tile-link">
|
||||
React Intro
|
||||
<div class="Tile-links">
|
||||
<a target="_blank" href="https://github.com/Microsoft/frontend-bootcamp/tree/master/step1-04/demo">demo</a> |
|
||||
<a target="_blank" href="./step1-04/demo/">demo</a> |
|
||||
<a target="_blank" href="./step1-04/final/">final</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
# Introduction To React
|
||||
# Introduction To React Demo
|
||||
|
||||
In our last example we saw how we could take a static HTML page and turn it into an interactive page with some buttons and their `onclick` handlers.
|
||||
|
||||
In this example we'll see how React turns that paradigm completely around. With React, the entire DOM is generated and maintained by JavaScript, directly inside the browser. This makes it easier to assemble your application out of reusable pieces, maintain state within a component, and pass data between them.
|
||||
|
||||
## Demo
|
||||
|
||||
In this demo we'll be creating a simple counter that will display a count and increment on click.
|
||||
|
||||
### The App
|
||||
## The App
|
||||
|
||||
This is the starting point of our React application. It is a component just like the other ones we will be building, but this component will only ever be used once, to render the application. Here's how our app starts out. Let's walk through each line:
|
||||
|
||||
@@ -17,9 +15,10 @@ import React from 'react';
|
||||
|
||||
export class App extends React.Component {
|
||||
render() {
|
||||
const text = 'My App';
|
||||
return (
|
||||
<div>
|
||||
<h2>My App</h2>
|
||||
<div className="App">
|
||||
<h2>{text != '' ? text : 'Default App Name'}</h2>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -33,17 +32,16 @@ export class App extends React.Component {
|
||||
- `return` - Remember that functions can return values in addition to having side effects, and this component is no different.
|
||||
|
||||
**Inside of the return?** It's HTML! Actually, it's [JSX](https://reactjs.org/docs/introducing-jsx.html), but with very few exceptions you can treat it like HTML. A few key differences:
|
||||
|
||||
- Since `class` is a [reserved word](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Keywords) in JavaScript, you will need to use `className` on your HTML tags: `<div className="foo">`
|
||||
- We can use custom HTML tags corresponding to the React components we create: `<div><MyControl>hi</MyControl></div>`
|
||||
- Controls can be self-closing: `<div><MyControl text='hi' /></div>`
|
||||
- You can use JavaScript inside of JSX! If you declare `const name = 'Micah';` inside the render function, you can use that variable inside of your JSX like `<div>{name}</div>` or `<div><MyControl text={name} /></div>`. This works with function calls and some types of conditionals as well.
|
||||
- You can use JavaScript inside of JSX!
|
||||
|
||||
### index.tsx
|
||||
## index.tsx
|
||||
|
||||
This is the file that places your App onto the page.
|
||||
|
||||
> Note that to avoid build errors, this file has been renamed to `index.temp`. Change the name to `index.tsx`.
|
||||
|
||||
```ts
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
@@ -57,13 +55,13 @@ ReactDOM.render(<App />, document.getElementById('app'));
|
||||
> This notation is called [object destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring), and it's awesome!
|
||||
- `ReactDOM.render...` - This line calls the render function inside of `ReactDOM` and attaches our `<App />` component to the element with `id=app`. Take a peek in the index.html file. Shouldn't be too hard to find it.
|
||||
|
||||
### Counter Component
|
||||
## Counter Component
|
||||
|
||||
In this example we'll start with an already scaffolded out control. The goal of our counter is to track how many times the counter button is clicked. In the past JavaScript demo we might have accessed the counter element using `document.querySelector('.counter')` and manually incremented the number found there. While using the DOM as your data store works, it's REALLY hard to scale past the most basic demo.
|
||||
|
||||
React solves this by allowing each control to specify its own data store, called **state**. We can reference values in state when we render our UI, and we can also update state over the lifetime of our application.
|
||||
|
||||
#### Adding State
|
||||
### Adding State
|
||||
|
||||
JavaScript uses a `constructor` method to instantiate each copy of a class. So for class-based controls, this is where we define an initial value for `state`.
|
||||
|
||||
@@ -107,7 +105,7 @@ const { counter } = this.state;
|
||||
const { text } = this.props;
|
||||
```
|
||||
|
||||
#### Adding JSX
|
||||
### Adding JSX
|
||||
|
||||
```jsx
|
||||
return (
|
||||
@@ -122,7 +120,7 @@ Each JSX return value needs to be a single element, so start with a wrapping `<d
|
||||
|
||||
Now let's see how we can use this component in our app.
|
||||
|
||||
#### Updating the App to Use Counters
|
||||
### Updating the App to Use Counters
|
||||
|
||||
Before we can use our `Counter`, we need to import it into the App file.
|
||||
|
||||
@@ -144,7 +142,7 @@ return (
|
||||
|
||||
> Note the capitalization of `Counter`. HTML might not be case-sensitive, but JSX is! A common practice is to use the capitalized names of HTML elements to name corresponding React components: Button, Select, Label, Form, etc.
|
||||
|
||||
### Exploring Component Props
|
||||
## Exploring Component Props
|
||||
|
||||
Now that we've got two counters on our page, we can see that the string passed into the `text` attribute got passed into our counter and rendered on the page. Being able to pass values (props) into controls makes them more flexible and reusable. Props can be strings, numbers, booleans, and even arrays and objects.
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
|
||||
export class App extends React.Component {
|
||||
render() {
|
||||
let text = 'My App';
|
||||
return (
|
||||
<div className="App">
|
||||
<h2>{text !== '' ? text : 'Default App Name'}</h2>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ import React from 'react';
|
||||
|
||||
export class Counter extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
|
||||
);
|
||||
return <p>hello</p>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Counter } from './components/Counter';
|
||||
export class App extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<div className="App">
|
||||
<h2>My App</h2>
|
||||
<Counter text="Chickens" />
|
||||
<Counter text="Ducks" />
|
||||
|
||||
@@ -3,7 +3,7 @@ import React from 'react';
|
||||
export const TodoFooter = (props: any) => {
|
||||
return (
|
||||
<footer>
|
||||
<p>footer</p>
|
||||
<div>Footer</div>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,6 +2,10 @@ import React from 'react';
|
||||
|
||||
export class TodoHeader extends React.Component {
|
||||
render() {
|
||||
return <div>Header</div>;
|
||||
return (
|
||||
<header>
|
||||
<div>Header</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,10 @@ import React from 'react';
|
||||
|
||||
export class TodoList extends React.Component<any, any> {
|
||||
render() {
|
||||
return <div />;
|
||||
return (
|
||||
<div>
|
||||
<span>List</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,9 +46,11 @@ constructor(props) {
|
||||
To avoid reaching into state over and over, we once again use destructuring to pull out the pieces we need.
|
||||
|
||||
```jsx
|
||||
const { filter, todos } = this.state;
|
||||
const { filter, todos = [] } = this.state;
|
||||
```
|
||||
|
||||
> Note that I've set `todos` to default to an empty array so that the `todos` variable is never undefined
|
||||
|
||||
Now we can pass `filter` and `todos` into our components.
|
||||
|
||||
```jsx
|
||||
@@ -99,7 +101,7 @@ In CSS-based styling, visual states are applied by adding and removing classes.
|
||||
|
||||
In traditional HTML forms, users interact with the form, and on submit, those values are captured and transmitted. Those are called **uncontrolled inputs**.
|
||||
|
||||
A **controlled input** is one whose value is defined by state, and interaction with that input updates state with each keystroke. This round trip process might sound inefficient, but in reality it has little to no impact, and it enables some advanced form functionality.
|
||||
A **controlled input** is one whose value is defined by state, and interaction with that input updates state with each keystroke. This round trip process might sound inefficient, but in reality it has little to no impact, and it makes forms much easier to work with.
|
||||
|
||||
To create a controlled component, we need two things, which our demo already provides:
|
||||
|
||||
@@ -122,3 +124,5 @@ With those two pieces in place, we can update our uncontrolled input to being co
|
||||
```jsx
|
||||
<input value={this.state.labelInput} onChange={this._onChange} className="textfield" placeholder="add todo" />
|
||||
```
|
||||
|
||||
> If you have React Dev Tools installed, open them up and take a look at labelInput as we type in the input.
|
||||
|
||||
@@ -3,11 +3,11 @@ import { TodoListItem } from './TodoListItem';
|
||||
|
||||
export class TodoList extends React.Component<any, any> {
|
||||
render() {
|
||||
const { filter, todos } = this.props;
|
||||
const { filter, todos = [] } = this.props;
|
||||
|
||||
// const filteredTodos = Object.keys(todos).filter(id => {
|
||||
// return filter === 'all' || (filter === 'completed' && todos[id].completed) || (filter === 'active' && !todos[id].completed);
|
||||
// });
|
||||
const filteredTodos = Object.keys(todos).filter(id => {
|
||||
return filter === 'all' || (filter === 'completed' && todos[id].completed) || (filter === 'active' && !todos[id].completed);
|
||||
});
|
||||
return (
|
||||
<ul className="todos">
|
||||
<TodoListItem />
|
||||
|
||||
@@ -29,7 +29,7 @@ export class TodoApp extends React.Component<any, any> {
|
||||
};
|
||||
}
|
||||
render() {
|
||||
const { filter, todos } = this.state;
|
||||
const { filter, todos = [] } = this.state;
|
||||
return (
|
||||
<div>
|
||||
<TodoHeader filter={filter} />
|
||||
|
||||
@@ -3,7 +3,7 @@ import { TodoListItem } from './TodoListItem';
|
||||
|
||||
export class TodoList extends React.Component<any, any> {
|
||||
render() {
|
||||
const { filter, todos } = this.props;
|
||||
const { filter, todos = [] } = this.props;
|
||||
|
||||
const filteredTodos = Object.keys(todos).filter(id => {
|
||||
return filter === 'all' || (filter === 'completed' && todos[id].completed) || (filter === 'active' && !todos[id].completed);
|
||||
|
||||
@@ -29,7 +29,7 @@ export class TodoApp extends React.Component<any, any> {
|
||||
};
|
||||
}
|
||||
render() {
|
||||
const { filter, todos } = this.state;
|
||||
const { filter, todos = [] } = this.state;
|
||||
return (
|
||||
<div>
|
||||
<TodoHeader filter={filter} />
|
||||
|
||||
@@ -3,7 +3,7 @@ import { TodoListItem } from './TodoListItem';
|
||||
|
||||
export class TodoList extends React.Component<any, any> {
|
||||
render() {
|
||||
const { filter, todos } = this.props;
|
||||
const { filter, todos = [] } = this.props;
|
||||
|
||||
const filteredTodos = Object.keys(todos).filter(id => {
|
||||
return filter === 'all' || (filter === 'completed' && todos[id].completed) || (filter === 'active' && !todos[id].completed);
|
||||
|
||||
@@ -22,11 +22,11 @@ If you've ever used [Sass](https://sass-lang.com/) you are familiar with this co
|
||||
|
||||
Let's dive into the demo and see how TypeScript can help us better understand our component props, and guard against future regressions.
|
||||
|
||||
## Demo
|
||||
# Demo
|
||||
|
||||
Let's start off in the TodoList, as that has the most data flow, up and down. There isn't any actionable UI in this component as we're simply passing `completed` down to each `TodoListItem`, but we can write a component interface to make sure that everything gets passed down properly.
|
||||
|
||||
### Writing TodoListProps
|
||||
## Writing TodoListProps
|
||||
|
||||
Looking at our `TodoApp` we know that `TodoList` has three props, `filter`, `todos`, and `complete`. We'll start by creating an interface that represents this component's props called `TodoListProps`.
|
||||
|
||||
@@ -50,11 +50,11 @@ export class TodoList extends React.Component<TodoListProps, any>
|
||||
|
||||
Now that we have a typed component, let's go back to our `TodoApp` and see what happens if we try to change the name of a prop.
|
||||
|
||||
### Adding type safety
|
||||
## Adding type safety
|
||||
|
||||
So far we've only established what our prop names are, not the values inside of them. Let's first look at `filter`, and see how we can improve that prop's type safety.
|
||||
|
||||
#### Filter Type
|
||||
### Filter Type
|
||||
|
||||
We know that filter shouldn't be an object, array or function, so we can specify it should always be a string like this:
|
||||
|
||||
@@ -78,7 +78,7 @@ interface TodoListProps {
|
||||
|
||||
Now try going back to `TodoApp` and changing the `filter` attribute in `TodoList` to something else.
|
||||
|
||||
#### Complete Type
|
||||
### Complete Type
|
||||
|
||||
The `complete` props isn't data, but rather a function. Fortunately, TypeScript can handle function types just as well as data.
|
||||
|
||||
@@ -92,7 +92,7 @@ interface TodoListProps {
|
||||
|
||||
For functions we are only concerned with the parameters passed in and the return value. You can see in the example above that the function takes in an `id` of type string, and returns `void`, which means it has no return.
|
||||
|
||||
### Todos Type
|
||||
## Todos Type
|
||||
|
||||
The `todos` prop is interesting in that `todos` is an object with a bunch of unknown keys. So here's what that interface would look like.
|
||||
|
||||
@@ -113,7 +113,7 @@ interface TodoListProps {
|
||||
|
||||
Now that our interface is complete, try changing the word 'all' in `filter === all` and see that VS Code will tell you this condition will always be false. Imagine you had a typo in that line and you couldn't understand why your filter wasn't working.
|
||||
|
||||
### Abstracting types
|
||||
## Abstracting types
|
||||
|
||||
Most of our components are going to need to add types for `todos` and `filter`, so it's a good thing that TypeScript allows us to abstract those. I've already written up and exported those shared types in the file `TodoApp.types.ts`, so we just need to import them and pull them into our interface.
|
||||
|
||||
@@ -127,7 +127,7 @@ interface TodoListProps {
|
||||
}
|
||||
```
|
||||
|
||||
### Updating TodoApp
|
||||
## Updating TodoApp
|
||||
|
||||
Our `TodoApp` doesn't take any props, but it does have state. We can use TypeScript to define that as well.
|
||||
|
||||
@@ -139,7 +139,7 @@ export class TodoApp extends React.Component<{}, { todos: Todos; filter: FilterT
|
||||
|
||||
> Note that the first value in `<>` always refers to props. Since `TodoApp` takes none, we'll set it to an empty object.
|
||||
|
||||
### Writing TodoListItemProps
|
||||
## Writing TodoListItemProps
|
||||
|
||||
Jumping down to the TodoListItem, as we start to write the TodoListItemProps we realize that two of the props, `label` and `completed` have already been defined in the `TodoItem` interface in `TodoApp.types`. So in the same way we can reuse individual types (`FilterTypes`), and extend upon entire interfaces.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user