Merge branch 'master' of github.com:Microsoft/frontend-bootcamp
This commit is contained in:
Коммит
9f96d098a1
|
@ -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>
|
||||
|
|
|
@ -30,7 +30,7 @@ export const TodoFooter = connect(
|
|||
return (
|
||||
<Stack horizontal horizontalAlign="space-between">
|
||||
<Text>
|
||||
{itemCount} item{itemCount > 1 ? 's' : ''} left
|
||||
{itemCount} item{itemCount === 1 ? '' : 's'} left
|
||||
</Text>
|
||||
<DefaultButton onClick={() => props.clear()}>Clear Completed</DefaultButton>
|
||||
</Stack>
|
||||
|
|
|
@ -1,10 +1,31 @@
|
|||
## HTML Demo
|
||||
# HTML Demo
|
||||
|
||||
HTML tags are the basis of all web applications. They give the page structure, and define the content within.
|
||||
|
||||
An HTML tag takes the following form:
|
||||
|
||||
```html
|
||||
<tag class="foo" onclick="myFunction()" otherAttributes="values"> </tag>
|
||||
```
|
||||
|
||||
HTML tags can also be nested to create a tree that we call the [Document Object Model](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Introduction)
|
||||
|
||||
```html
|
||||
<div class="my-page">
|
||||
<h1>My Page</h1>
|
||||
<ul class="link">
|
||||
<li><a href="https://github.com/Microsoft/frontend-bootcamp"> Frontend Bootcamp </a></li>
|
||||
<li><a href="https://twitter.com/micahgodbolt"> @micahgodbolt </a></li>
|
||||
<li><a href="https://twitter.com/kenneth_chau"> @kenneth_chau</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
```
|
||||
|
||||
The [HTML demo page](https://microsoft.github.io/frontend-bootcamp/step1-01/html-demo/html-demo.html) is a large collection of HTML elements that you will come across during development. The full list of elements can be found on [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element).
|
||||
|
||||
To learn more about each element, click on the element names below.
|
||||
|
||||
### [Document Metadata](https://developer.mozilla.org/en-US/docs/Web/HTML/Element#Document_metadata)
|
||||
## [Document Metadata](https://developer.mozilla.org/en-US/docs/Web/HTML/Element#Document_metadata)
|
||||
|
||||
- [`html`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/html) - Root-level element
|
||||
- [`head`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head) - Provides general information (metadata) about the page
|
||||
|
@ -12,7 +33,7 @@ To learn more about each element, click on the element names below.
|
|||
- [`link`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link) - Links to external resources (usually stylesheets)
|
||||
- [`style`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style) - Inline style tag
|
||||
|
||||
### [Content Sectioning](https://developer.mozilla.org/en-US/docs/Web/HTML/Element#Content_sectioning)
|
||||
## [Content Sectioning](https://developer.mozilla.org/en-US/docs/Web/HTML/Element#Content_sectioning)
|
||||
|
||||
- [`section`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/section) - Generic section of content
|
||||
- [`header`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/header) - Introductory content or navigational aid
|
||||
|
@ -23,7 +44,7 @@ To learn more about each element, click on the element names below.
|
|||
- [`aside`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/aside) - Related information
|
||||
- [`h1`,`h2`,`h3`,`h4`,`h5`,`h6`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Heading_Elements) - Section headings
|
||||
|
||||
### [Block Text Content](https://developer.mozilla.org/en-US/docs/Web/HTML/Element#Text_content)
|
||||
## [Block Text Content](https://developer.mozilla.org/en-US/docs/Web/HTML/Element#Text_content)
|
||||
|
||||
- [`div`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div) - Generic block level container
|
||||
- [`p`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/p) - Paragraph
|
||||
|
@ -32,7 +53,7 @@ To learn more about each element, click on the element names below.
|
|||
- [`li`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/li) - List item
|
||||
- [`pre`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/pre) - Preformatted text
|
||||
|
||||
### [Inline Text Elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Element#Inline_text_semantics)
|
||||
## [Inline Text Elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Element#Inline_text_semantics)
|
||||
|
||||
- [`a`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a) - Anchor element for creating links to other pages, files, programs
|
||||
- [`span`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/span) - Generic inline container
|
||||
|
@ -43,11 +64,11 @@ To learn more about each element, click on the element names below.
|
|||
- [`sup`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sup) - Superscript text
|
||||
- [`code`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/code) - Fragment of computer code (monospace)
|
||||
|
||||
### [Image and multimedia](https://developer.mozilla.org/en-US/docs/Web/HTML/Element#Inline_text_semantics)
|
||||
## [Image and multimedia](https://developer.mozilla.org/en-US/docs/Web/HTML/Element#Inline_text_semantics)
|
||||
|
||||
- [`img`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img) - Embeds image into document
|
||||
|
||||
### [Table Content](https://developer.mozilla.org/en-US/docs/Web/HTML/Element#Table_content)
|
||||
## [Table Content](https://developer.mozilla.org/en-US/docs/Web/HTML/Element#Table_content)
|
||||
|
||||
- [`table`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table) - Root table container
|
||||
- [`thead`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/thead) - Table head container
|
||||
|
@ -58,7 +79,7 @@ To learn more about each element, click on the element names below.
|
|||
|
||||
> We used to use tables to lay out applications. Each cell would be filled with slices of images from Photoshop or Fireworks. Rounded corners were created by elaborate table tricks
|
||||
|
||||
### [Forms](https://developer.mozilla.org/en-US/docs/Web/HTML/Element#Forms)
|
||||
## [Forms](https://developer.mozilla.org/en-US/docs/Web/HTML/Element#Forms)
|
||||
|
||||
- [`form`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form) - Form container
|
||||
- [`label`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label) - Label text for form elements
|
||||
|
@ -73,6 +94,6 @@ To learn more about each element, click on the element names below.
|
|||
- `radio`
|
||||
- `submit`
|
||||
|
||||
## Next Step
|
||||
# Next Step
|
||||
|
||||
[CSS Demo](../css-demo)
|
||||
|
|
|
@ -63,7 +63,7 @@ function displayMatches() {
|
|||
|
||||
### Iteration
|
||||
|
||||
Next we'll update our function to iterate through a string of letters. We loop over each letter using the [`for of`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of) syntax. We'll use real input later, but for now this varifies that our function is working.
|
||||
Next we'll update our function to iterate through a string of letters. We loop over each letter using the [`for of`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of) syntax. We'll use real input later, but for now this verifies that our function is working.
|
||||
|
||||
```js
|
||||
function displayMatches() {
|
||||
|
@ -90,7 +90,7 @@ function displayMatches() {
|
|||
}
|
||||
```
|
||||
|
||||
> In JavaScript, it's safest to use strict `===` for comparisons, because `==` will try to convert the operands to the same type (and sometimes the behavior is [not what you'd expect](https://www.youtube.com/watch?v=et8xNAc2ic8)). For example, `"1" == 1` is true whereas `"1" === 1` would be false.
|
||||
> In JavaScript, it's safest to use strict `===` for comparisons, because `==` will try to convert the operands to the same type. For example, `"1" == 1` is true whereas `"1" === 1` is false, but the behavior in certain other cases is [not what you'd expect](https://www.youtube.com/watch?v=et8xNAc2ic8). (See [this video](https://www.destroyallsoftware.com/talks/wat) for more strange JavaScript behavior.)
|
||||
|
||||
### Interacting with the DOM
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ Every website, application, form or component starts with markup. The HTML will
|
|||
|
||||
## Demo
|
||||
|
||||
In this exercise we will scaffold out some HTML for our Todo app, then add some basic styling to it.
|
||||
In this exercise we will scaffold out some HTML for our todo app, then add some basic styling to it.
|
||||
|
||||
### Page scaffold
|
||||
|
||||
|
@ -20,14 +20,14 @@ In this exercise we will scaffold out some HTML for our Todo app, then add some
|
|||
</html>
|
||||
```
|
||||
|
||||
1. The DOCTYPE tells the browser that this file is written in modern HTML.
|
||||
2. The HTML tag wraps the entire page, and is the page root. Nothing is placed outside of those tags. Attributes can be set on HTML.
|
||||
3. Head will contain all of the page's meta data, in this case a link to our CSS file.
|
||||
4. Body is where all of the visible content should be placed.
|
||||
1. The [`DOCTYPE`](https://developer.mozilla.org/en-US/docs/Glossary/Doctype) tells the browser that this file is written in modern HTML.
|
||||
2. The `html` tag wraps the entire page and is the page root. Nothing is placed outside of this tag.
|
||||
3. `head` will contain all of the page's metadata, in this case a link to our CSS file.
|
||||
4. `body` is where all of the visible content should be placed.
|
||||
|
||||
### Content Sectioning
|
||||
|
||||
As we saw in the previous demo, HTML elements can be used to describe different content sections of the applications. Let's add `header`, `main` and `footer`, as well as populate the header with an `h1`, addTodo div, and `nav` for our filters.
|
||||
As we saw in the previous demo, HTML elements can be used to describe different content sections of the applications. Let's add `header`, `main` and `footer`, as well as populate the header with an `h1`, addTodo `div`, and `nav` for our filters.
|
||||
|
||||
```html
|
||||
<body>
|
||||
|
@ -41,11 +41,11 @@ As we saw in the previous demo, HTML elements can be used to describe different
|
|||
</body>
|
||||
```
|
||||
|
||||
> Note that a `form` element would have been more semantic than a `div`, but we aren't using this form to POST to a server, so for this example a div is easier to use.
|
||||
> Note that a `form` element would have been more semantically correct than a `div`, but we aren't using this form to POST to a server, so for this example a div is easier to use.
|
||||
|
||||
### Updating the header
|
||||
|
||||
The header of our page is where most of the action is going to happen. First, lets give our app a name, adding 'TODO' to our `h1`. Then we can add an input and button to our `addTodo` div.
|
||||
The header of our page is where most of the action will happen. First, let's give our app a name, adding "TODO" to our `h1`. Then we can add an input and button to our `addTodo` div.
|
||||
|
||||
```html
|
||||
<input class="textfield" placeholder="add todo" /> <button class="submit">Add</button>
|
||||
|
@ -53,7 +53,7 @@ The header of our page is where most of the action is going to happen. First, le
|
|||
|
||||
#### Navigation
|
||||
|
||||
The navigation for this application is quite simple. We want users to be able to switch between three filtered states. Since we need to track which state is currently selected, we'll add that as a class on the first item.
|
||||
The navigation for this application is quite simple. We want users to be able to switch between three filtered states. Since we need to track which state is currently selected, we'll add a `selected` class to the first item.
|
||||
|
||||
```html
|
||||
<nav class="filter">
|
||||
|
@ -65,7 +65,7 @@ The navigation for this application is quite simple. We want users to be able to
|
|||
|
||||
### Adding styles
|
||||
|
||||
Now that we've got the top of our application scaffolded, we can add some styles in the head.
|
||||
Now that we've got the top of our application scaffolded, we can add some styles in the `head`.
|
||||
|
||||
```html
|
||||
<head>
|
||||
|
@ -77,7 +77,7 @@ Now that we've got the top of our application scaffolded, we can add some styles
|
|||
|
||||
It looks like the selected button isn't getting any special styles. Let's dig in and see why that is.
|
||||
|
||||
Open up the browser inspector and target our 'all' button. You'll notice that the blue style is present on the list, but it is being overridden by the `border: none` above it. This is a situation where specificity is winning out over the cascade.
|
||||
Open up the browser inspector and target our "all" button. You'll notice that the blue style is present on the list, but it is being overridden by the `border: none` above it. This is a situation where specificity is winning out over the cascade.
|
||||
|
||||
> **Cascade** states that if two selectors are equal, the lowest one on the page wins
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
this page is intentionally blank
|
|
@ -4,7 +4,7 @@
|
|||
2. Add 4 list items with class `todo` inside of that list with the following content
|
||||
`<label><input type="checkbox" /> <span class="title"> Todo </span> </label>`
|
||||
3. Add a span and a button to your footer
|
||||
4. Span content should be `4 items left` and button should say `Clear Completed` and have a class of `submit`
|
||||
4. Span content should be `<i class="remaining">4</i> items left` and button should say `Clear Completed` and have a class of `submit`
|
||||
5. Go into the CSS file and add `display: flex` to the footer. Also add `flex-grow:1` to the span inside of the footer
|
||||
|
||||
> Hint: Look back at the CSS demo to see the various ways you can use selectors to target existing HTML
|
||||
|
|
|
@ -32,6 +32,8 @@
|
|||
</li>
|
||||
</ul>
|
||||
</main>
|
||||
<footer><span>4 items left</span> <button class="submit">Clear Completed</button></footer>
|
||||
<footer>
|
||||
<span><i class="remaining">4</i> items left</span> <button class="submit">Clear Completed</button>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,36 +1,96 @@
|
|||
# JavaScript Demo
|
||||
|
||||
Now that we a UI that looks like a todo app, we need to add functionality to make it **function** like a todo app. In this example we are going to use raw JavaScript to explicitly modify our application as we interact with it. This will be in stark contrast to the implicit approach we will take when we do this with React in the next exercise.
|
||||
Now that we have a UI that looks like a todo app, we need to make it **function** like a todo app. In this example we are going to use raw JavaScript to explicitly modify our application as we interact with it. This will be in stark contrast to the implicit approach we will take when we do this with React in the next exercise.
|
||||
|
||||
> Keep an eye on how often user actions directly modify the HTML on the page. You'll see this number drop to zero when we start using React.
|
||||
|
||||
## Demo
|
||||
## What we're starting with
|
||||
|
||||
This demo starts off with a few elements already in place. Let's walk through what's already here.
|
||||
This demo starts off with a few functions already in place. Let's walk through what's already here.
|
||||
|
||||
- **clearInput()** - This is a generic, reusable function that takes in a `selector` parameter, finds the first matching element, and sets the element's value to an empty string. This direct modification is called a side effect.
|
||||
- **getTodoText()** - This is a quick helper function that returns the value inside of our textfield. Notice how some functions return values and how you can set that return to a variable.
|
||||
- **filter()** - This function takes in a `filterName` string, and a `button` which is a reference to the clicked button.
|
||||
1. Remove any `selected` class names.
|
||||
- `clearInput()` - This is a generic, reusable function that takes in a `selector` parameter, finds the first matching element, and sets the element's value to an empty string. This direct modification is called a **side effect**.
|
||||
- `getTodoText()` - This is a helper function that returns the value inside of our text field. Notice how some functions return values and how you can save that return value in a variable.
|
||||
- `filter()` - This function takes in a `filterName` string, and a `button` which is a reference to the clicked button.
|
||||
1. Remove the `selected` class from the previously selected element.
|
||||
2. Add `selected` to the clicked button.
|
||||
3. Set `filterName` to the clicked button's `innerText` value.
|
||||
4. Get all of the todos with [querySelectAll](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll), and then loop through them.
|
||||
4. Get all of the todos with [`querySelectAll`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll), and then loop through them.
|
||||
5. Set the `hidden` property of each todo based on the filter/state combination.
|
||||
|
||||
### Writing addTodo Function
|
||||
## Writing `addTodo` Function
|
||||
|
||||
1. `todo` is set to equal the first todo item.
|
||||
2. `newTodo` is a clone of todo. Passing true means it is a deep clone, so we get the todo's children as well. Cloning does not duplicate the DOM node. We'll need to insert it in step 4.
|
||||
> Note that this approach is very fragile, as it requires a todo node to always be present on the page.
|
||||
3. We set the innerText of the `<span class='title'>` to the value returned from getTodoText.
|
||||
> Note that if we left off the `()` we'd actually be assigning innerText to the 'function' instead of the function return.
|
||||
4. Insert our new todo into the todo's parent (the `ul`), before our reference todo. [insertBefore](https://developer.mozilla.org/en-US/docs/Web/API/Node/insertBefore)
|
||||
We start writing all functions with the `function` keyword and the name of our function. Functions can take parameters, but in this case we don't need to pass any through, so we follow the function name with an empty `()`. Everything we want this function to do will then be placed in a set of brackets `{}`.
|
||||
|
||||
### Triggering functions from click events
|
||||
```js
|
||||
function addTodo() {}
|
||||
```
|
||||
|
||||
### Creating a Todo Clone
|
||||
|
||||
The first thing we need to do in this function is create a `newTodo` wish is a clone of an existing Todo.
|
||||
|
||||
```js
|
||||
function addTodo() {
|
||||
const todo = document.querySelector('.todo');
|
||||
const newTodo = todo.cloneNode(true);
|
||||
}
|
||||
```
|
||||
|
||||
Passing true to our `cloneNode` means it is a deep clone, so we get a copy of the todo's children as well.
|
||||
|
||||
> Note that this approach is very fragile, as it requires a todo node to always be present on the page.
|
||||
|
||||
### Updating the newTodos's text
|
||||
|
||||
With this clone created, we need to update the `innerText` of the node with our todo text, which is returned from `getTodoText()`.
|
||||
|
||||
```js
|
||||
function addTodo() {
|
||||
const todo = document.querySelector('.todo');
|
||||
const newTodo = todo.cloneNode(true);
|
||||
|
||||
newTodo.querySelector('.title').innerText = getTodoText();
|
||||
}
|
||||
```
|
||||
|
||||
We can target a child node by calling `querySelector` again and asking for the child with the `.child` class.
|
||||
|
||||
> Note that if we left off the `()` we'd actually be assigning innerText to the 'function' instead of the function return.
|
||||
|
||||
### Placing the newTodo into the list of todos
|
||||
|
||||
Making a clone only stores the clone inside of our variable. If we want to place it back into the DOM, we'll need to insert it manually. For that we can use [insertBefore](https://developer.mozilla.org/en-US/docs/Web/API/Node/insertBefore).
|
||||
|
||||
This function actually needs to target the parent element, which we can get by calling `todo.parentElement` and passing parameters of `(elementToInsert, elementToInsertBefore)`.
|
||||
|
||||
```js
|
||||
function addTodo() {
|
||||
const todo = document.querySelector('.todo');
|
||||
const newTodo = todo.cloneNode(true);
|
||||
newTodo.querySelector('.title').innerText = getTodoText();
|
||||
todo.parentElement.insertBefore(newTodo, todo);
|
||||
}
|
||||
```
|
||||
|
||||
### Cleanup
|
||||
|
||||
Now that our todo has been inserted into the DOM, we can clear the text input and call `updateRemaining()`.
|
||||
|
||||
```js
|
||||
function addTodo() {
|
||||
...
|
||||
clearInput('.textfield');
|
||||
updateRemaining();
|
||||
}
|
||||
```
|
||||
|
||||
> Note how often we have to reach into the DOM to find nodes, manipulate content, insert back into the DOM and manually change the values in inputs. This is the error prone manipulation that React helps us avoid.
|
||||
|
||||
## Triggering functions from click events
|
||||
|
||||
Now that we have a working `addTodo` function, we need a way to trigger it when the user is ready. This can be done in two ways.
|
||||
|
||||
1. We can find the element with querySelector, then set its `onclick` to our function
|
||||
1. We can find the element with `querySelector`, then set its `onclick` to our function
|
||||
|
||||
```js
|
||||
document.querySelector('.addTodo .submit').onclick = addTodo;
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
</li>
|
||||
</ul>
|
||||
<footer>
|
||||
<span><span class="remaining">4</span> items left</span>
|
||||
<span><i class="remaining">4</i> items left</span>
|
||||
<button class="submit">Clear Completed</button>
|
||||
</footer>
|
||||
</body>
|
||||
|
@ -52,12 +52,12 @@
|
|||
|
||||
const filterName = button.innerText;
|
||||
for (let todo of document.querySelectorAll('.todo')) {
|
||||
const checked = todo.querySelector('input').checked == true;
|
||||
if (filterName == 'all') {
|
||||
const checked = todo.querySelector('input').checked === true;
|
||||
if (filterName === 'all') {
|
||||
todo.hidden = false;
|
||||
} else if (filterName == 'active') {
|
||||
} else if (filterName === 'active') {
|
||||
todo.hidden = checked;
|
||||
} else if (filterName == 'completed') {
|
||||
} else if (filterName === 'completed') {
|
||||
todo.hidden = !checked;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,23 +2,23 @@
|
|||
|
||||
### Update Navigation
|
||||
|
||||
1. Add an onclick attribute to all 3 buttons in the navigation.
|
||||
2. For each onclick call the `filter` function. In our function we need a reference to the clicked button, so pass in the keyword `this` as the only parameter.
|
||||
1. Add an `onclick` attribute to all three buttons in the navigation.
|
||||
2. In each `onclick` call the `filter` function. In our function we need a reference to the clicked button, so pass in the keyword `this` as the only parameter.
|
||||
|
||||
### Write updateRemaining function
|
||||
### Write an `updateRemaining` function
|
||||
|
||||
1. Get a reference to the span with the `remaining` class, and store it in a variable.
|
||||
2. Use [querySelectorAll](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll) to get all of the todos.
|
||||
2. Use [`querySelectorAll`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll) to get all of the todos.
|
||||
3. Set the `innerText` of the remaining span to the length of those todos.
|
||||
4. Add updateRemaining() to end of addTodo function.
|
||||
4. Add `updateRemaining()` to the end of the `addTodo` function.
|
||||
|
||||
### Write a clearCompleted function
|
||||
### Write a `clearCompleted` function
|
||||
|
||||
1. Get a reference to all of the todos with [querySelectorAll](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll).
|
||||
1. Get a reference to all of the todos with [`querySelectorAll`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll).
|
||||
2. Use a `for (let todo of todos)` loop to iterate over each todo.
|
||||
3. Inside the for loop write an `if` statement to test if the `input` inside of the todo has a checked value of true.
|
||||
> Hint: you can use querySelector on any HTML node to find child elements within.
|
||||
4. Call `todo.remove()` for any todo whos input check is true.
|
||||
> Hint: you can use `querySelector` on any HTML element to find matching child elements.
|
||||
4. Call `todo.remove()` for any todo whose input is checked.
|
||||
5. After the loop is done, run `updateRemaining()`.
|
||||
6. Attach this function to the footer button.
|
||||
6. Attach `clearCompleted()` function to the `onclick` of the footer button.
|
||||
7. Test it out!
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
</li>
|
||||
</ul>
|
||||
<footer>
|
||||
<span><span class="remaining">4</span> items left</span>
|
||||
<span><i class="remaining">4</i> items left</span>
|
||||
<button class="submit">Clear Completed</button>
|
||||
</footer>
|
||||
</body>
|
||||
|
@ -70,12 +70,12 @@
|
|||
|
||||
const filterName = button.innerText;
|
||||
for (let todo of document.querySelectorAll('.todo')) {
|
||||
const checked = todo.querySelector('input').checked == true;
|
||||
if (filterName == 'all') {
|
||||
const checked = todo.querySelector('input').checked === true;
|
||||
if (filterName === 'all') {
|
||||
todo.hidden = false;
|
||||
} else if (filterName == 'active') {
|
||||
} else if (filterName === 'active') {
|
||||
todo.hidden = checked;
|
||||
} else if (filterName == 'completed') {
|
||||
} else if (filterName === 'completed') {
|
||||
todo.hidden = !checked;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
</li>
|
||||
</ul>
|
||||
<footer>
|
||||
<span><span class="remaining">4</span> items left</span>
|
||||
<span><i class="remaining">4</i> items left</span>
|
||||
<button onclick="clearCompleted()" class="submit">Clear Completed</button>
|
||||
</footer>
|
||||
</body>
|
||||
|
@ -65,7 +65,7 @@
|
|||
function clearCompleted() {
|
||||
const todos = document.querySelectorAll('.todo');
|
||||
for (let todo of todos) {
|
||||
if (todo.querySelector('input').checked == true) {
|
||||
if (todo.querySelector('input').checked === true) {
|
||||
todo.remove();
|
||||
}
|
||||
}
|
||||
|
@ -78,12 +78,12 @@
|
|||
|
||||
const filterName = button.innerText;
|
||||
for (let todo of document.querySelectorAll('.todo')) {
|
||||
const checked = todo.querySelector('input').checked == true;
|
||||
if (filterName == 'all') {
|
||||
const checked = todo.querySelector('input').checked === true;
|
||||
if (filterName === 'all') {
|
||||
todo.hidden = false;
|
||||
} else if (filterName == 'active') {
|
||||
} else if (filterName === 'active') {
|
||||
todo.hidden = checked;
|
||||
} else if (filterName == 'completed') {
|
||||
} else if (filterName === 'completed') {
|
||||
todo.hidden = !checked;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,33 +15,33 @@ 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>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- **import React from 'react';** - This is how we [import modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) in JavaScript. This line creates a variable in this file called `React` that is equal to the default `export` of the `react` npm module.
|
||||
- **export class App** - Just like react exports code, our App component is nothing more than an exported "App" class. This allows us to import the class into other files.
|
||||
- **extends React.Component** - A JavaScript class is similar to other programming languages (it's a collection of methods and properties). Classes can also be extended, so when we create a React component class, we always extend the base React.Component class. Note that this `Component` class is coming from the `React` variable imported up top.
|
||||
> Note that `<any, any>` is necessary for TypeScript which we will touch on later.
|
||||
- **render()** - One of the methods defined by React.Component is the `render()` method. This is a function that defines the HTML the component is going to render.
|
||||
- **return** - Remember that functions can return values in addition to side effects, and this component is no different.
|
||||
- **Inside of the return?** It's HTML! Actually, it's JSX, but with very few exceptions you can treat it like HTML. A few key differences:
|
||||
1. Since 'class' is a reserved word in JavaScript, you will need to use className on your HTML tags `<div className="foo">`
|
||||
2. We can use custom HTML tags created by these render functions `<div><MyControl>hi</MyControl></div>`
|
||||
3. Controls can be self closing `<div><MyControl text='hi' /></div>`
|
||||
4. 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 `<div>{name}</div>` or `<div><MyControl text={name} /></div>`. Works with functions, loops, conditionals as well.
|
||||
- `import React from 'react';` - This is how we [import modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) in JavaScript. This line creates a variable in this file called `React` that is equal to the default `export` of the `react` npm module.
|
||||
- `export class App` - Just like React exports code, our App component is nothing more than an exported `App` class. This allows us to import the class into other files.
|
||||
- `extends React.Component` - A JavaScript class is similar to a class in other programming languages (it's a collection of methods and properties). Classes can also be extended, so when we create a React component class, we always extend the base `React.Component` class. (Note that this `Component` class is coming from the `React` variable imported up top.)
|
||||
- `render()` - One of the methods defined by `React.Component` is the `render()` method, which defines the HTML the component is going to render.
|
||||
- `return` - Remember that functions can return values in addition to having side effects, and this component is no different.
|
||||
|
||||
### index.tsx
|
||||
**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!
|
||||
|
||||
## 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';
|
||||
|
@ -51,21 +49,21 @@ import { App } from './App';
|
|||
ReactDOM.render(<App />, document.getElementById('app'));
|
||||
```
|
||||
|
||||
- **import ReactDOM from "react-dom";** - We've seen React imported before, but now we're also grabbing ReactDom from a package called "react-dom".
|
||||
> Note that this casing is intentional. NPM packages are kabab-case, exported items are usually camelCase or PascalCase. PascalCase is usually used for 'proper noun' exports. ProjectName, ComponentName etc.
|
||||
- **import { App } from "./App";** - If we had exported our app like this: `export default class extends React.Component`, this line would look like the lines above - `import App from "./App";`. But React convention is to use named exports, which can easily be extracted like this `{ App }`.
|
||||
- `import ReactDOM from "react-dom";` - We've seen React imported before, but now we're also grabbing `ReactDOM` from a package called `react-dom`.
|
||||
> Note that this casing is intentional. Usually, NPM packages are kebab-case and exported items are camelCase or PascalCase. PascalCase is usually used for "proper noun" exports: ProjectName, ComponentName, etc.
|
||||
- `import { App } from './App';` - If we had exported our app using `export default class App extends React.Component`, this line would look similar to the lines above - `import App from './App';`. But the convention for React components is to use named exports, which can easily be extracted using syntax like `{ 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 peak in the index.html file. Shouldn't be too hard to find it.
|
||||
- `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 keep track of how many times the counter button is clicked. In the past JavaScript demo we might grab a reference to `document.querySelector('.counter')` and then manually increment the number we find there. While using the DOM as you data store works, it's REALLY hard to scale past the most basic demo.
|
||||
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.
|
||||
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 specify 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`.
|
||||
|
||||
```js
|
||||
constructor(props) {
|
||||
|
@ -76,13 +74,13 @@ constructor(props) {
|
|||
}
|
||||
```
|
||||
|
||||
- The constructor takes in the component props (values passed into the control).
|
||||
- the [super()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/super) function is called to gain access to some functionality in React.Component
|
||||
- Now we can define any state variables we want to use in the control, and give them a default value. This counter value can now be accessed via `this.state.counter`. We can also update state by calling `this.setState({counter: 1})`
|
||||
- The constructor takes in the component's props (values passed into the control).
|
||||
- The [`super()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/super) function calls the constructor of the parent class (in this case `React.Component`) to do any shared setup.
|
||||
- Now we can define any state variables we want to use in the control and give them a default value. Our counter value can now be accessed via `this.state.counter`. Later, we can update state by calling `this.setState({ counter: 1 })`.
|
||||
|
||||
#### Using [object destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring) for props and state
|
||||
|
||||
Both props are state are JavaScript objects. They have a bunch of key value pairs in them which you can access via `this.props.foo` or `this.state.bar`. Sometimes they have **MANY** values inside of them which you need access to. You could do this:
|
||||
Both `props` are `state` are JavaScript objects. They have a bunch of key/value pairs in them which you can access via `this.props.foo` or `this.state.bar`. Sometimes they have MANY values inside of them which you need access to. You could do this:
|
||||
|
||||
```js
|
||||
let cat = this.props.cat;
|
||||
|
@ -92,22 +90,22 @@ let pig = this.props.pig;
|
|||
let cow = this.props.cow;
|
||||
```
|
||||
|
||||
> Note that we access props and state on `this`, which is how you reference all of the class properties and methods.
|
||||
> Note that we access `props` and `state` on `this`, which is how you reference all class properties and methods.
|
||||
|
||||
But this is verbose and repetitive. Instead you can use destructuring to turn this into a one liner.
|
||||
But this is verbose and repetitive. Instead you can use destructuring to turn this into a one-liner.
|
||||
|
||||
```js
|
||||
let { cat, dog, bird, pig, cow } = this.props;
|
||||
```
|
||||
|
||||
So even though this isn't 100% necessary today, it does future proof our code if we add more props or state later. So let's add this inside of the render method, but above the return:
|
||||
Even though this isn't 100% necessary today, it does future-proof our code if we add more values to `props` or `state` later. So let's add this inside of the `render` method, above the `return`:
|
||||
|
||||
```js
|
||||
const { counter } = this.state;
|
||||
const { text } = this.props;
|
||||
```
|
||||
|
||||
#### Adding JSX
|
||||
### Adding JSX
|
||||
|
||||
```jsx
|
||||
return (
|
||||
|
@ -118,9 +116,11 @@ return (
|
|||
);
|
||||
```
|
||||
|
||||
Each JSX return needs to be a single element, so start with a wrapping `<div>`. Inside of that we can add the `text` we get from `this.props`, then after a colon, the `counter` we pulled in from `this.state`. This will render as the string `My Text Prop: 0`. After that let's add a button we'll use later. For now, we're going to see how we can use this in our app.
|
||||
Each JSX return value needs to be a single element, so start with a wrapping `<div>`. Inside of that we can add the `text` we get from `this.props`, then after a colon, the `counter` we pulled in from `this.state`. This will render as the string `My Text Prop: 0`. After that let's add a button we'll use later.
|
||||
|
||||
#### Updating the App to use Counters
|
||||
Now let's see how we can use this component in our app.
|
||||
|
||||
### Updating the App to Use Counters
|
||||
|
||||
Before we can use our `Counter`, we need to import it into the App file.
|
||||
|
||||
|
@ -128,7 +128,7 @@ Before we can use our `Counter`, we need to import it into the App file.
|
|||
import { Counter } from './components/Counter';
|
||||
```
|
||||
|
||||
Now that we have access to `Counter`, we can add it to the App just as if it were an HTML element.
|
||||
Now that we have access to `Counter`, we can use it in the App just as if it were an HTML element.
|
||||
|
||||
```jsx
|
||||
return (
|
||||
|
@ -140,11 +140,11 @@ return (
|
|||
);
|
||||
```
|
||||
|
||||
> Note the capitalization of Counter. HTML might not be case sensitive, but JSX is! A common practice is to use the capitalized versions of HTML elements to name their JSX counterpart. Button, Select, Label, Form etc.
|
||||
> 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 into controls make them more flexible and reusable. Props can be strings, numbers, booleans, and even arrays and objects.
|
||||
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.
|
||||
|
||||
```jsx
|
||||
<MyComponent
|
||||
|
@ -160,13 +160,15 @@ Now that we've got two Counters on our page, we can see that the string passed i
|
|||
/>
|
||||
```
|
||||
|
||||
> Note that all non string values are passed through as JavaScript by wrapping it in `{}`
|
||||
> Note that all non-string values are passed through as JavaScript by wrapping them in `{}`.
|
||||
|
||||
### Writing our Button Click
|
||||
### Writing our Button Click Handler
|
||||
|
||||
Our next step is to wire up the button to increment the `counter` in our component state. This will very similar to what we did in step 3, but instead of placing the function in a script tag, we can create it as a class method, and keep it out of the global scope.
|
||||
|
||||
> By convention we place methods below render, and private methods (those for internal use only) are prefixed with an underscore.
|
||||
> By convention we place other methods below render, and private methods (those for internal use only) are prefixed with an underscore.
|
||||
|
||||
This function will update our component's state, incrementing the counter value by 1. (Note that `setState` only modifies the values of keys listed in object passed as its parameter.)
|
||||
|
||||
```jsx
|
||||
_onButtonClick = () => {
|
||||
|
@ -174,25 +176,21 @@ _onButtonClick = () => {
|
|||
};
|
||||
```
|
||||
|
||||
This function will update our component state, incrementing our counter value by 1. Note that setState only affects the values of keys we have listed.
|
||||
> This isn't exactly a method, but a class property that is set to an [arrow function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions). This mostly works the same as `onButtonClick() { }` but eliminates the need for [extra boilerplate](https://medium.freecodecamp.org/this-is-why-we-need-to-bind-event-handlers-in-class-components-in-react-f7ea1a6f93eb) used to avoid potential "gotchas" with [how `this` works in JavaScript](https://codeburst.io/javascript-the-keyword-this-for-beginners-fb5238d99f85).)
|
||||
|
||||
> Note that this isn't exactly a method, but a property that is equal to a [arrow function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions). This works just as well as `onButtonClick() { }`, but doesn't require extra binding up in the constructor.
|
||||
|
||||
> Also note that `setState()` may update the state [asynchronously](https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous). If the next state depends on the previous state or props, then we should use the second form of `setState()` which takes a function that receives the previous state as the first argument and the props as the second.
|
||||
|
||||
Now that we have a function to increment out count, all that we have left is to connect it to our button.
|
||||
Now that we have a function to increment our count, all that's left is to connect it to our button.
|
||||
|
||||
```jsx
|
||||
<button onClick={this._onButtonClick}>Click</button>
|
||||
```
|
||||
|
||||
> Note the syntax is a bit different than HTML. `onclick="funcName()"` vs `onClick={this.funcName}`
|
||||
> Note the syntax is a bit different than in HTML: `onclick="funcName()"` in HTML vs `onClick={this.funcName}` in JSX.
|
||||
|
||||
> Also note that each Counter maintains its own state! You can modify the state inside of one without affecting the others.
|
||||
> Also note that each Counter maintains its own state! You can modify the state inside of one counter without affecting the others.
|
||||
|
||||
## Bonus: Using a Button component
|
||||
|
||||
Buttons are one of the most common components to write. They help abstract common styling, add icons or other decorations, and increase functionality (menu buttons etc). Using an existing Button component is as easy as importing it `import {Button} from './Button';` and replacing `<button></button>` with `<Button></Button>`. Let's take a quick look at Button to see how it came together.
|
||||
Buttons are among the most commonly written components. Custom buttons help abstract common styling, add icons or other decorations, and increase functionality (menu buttons etc). Let's take a quick look at a custom button component to see how it comes together.
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
|
@ -207,9 +205,9 @@ export const Button = props => {
|
|||
};
|
||||
```
|
||||
|
||||
- All controls need to import React (don't worry, only 1 copy ever gets into your app).
|
||||
- Importing CSS files into the component means that the CSS is only loaded if the component is used.
|
||||
- React components can be created as a class **or** as a function. In this function, props are passed in as a function parameter.
|
||||
> Until recently, you could only access state in class based components. But with the advent of [hooks](https://reactjs.org/docs/hooks-intro.html) you can create stateful function components.
|
||||
- Since this is a function, we don't have any methods, including `render()`. Just return your JSX as you would in a class based component.
|
||||
- `props.children` is anything passed between the opening and closing tags `<Button>I'm children</Button>`
|
||||
- All components need to import React (don't worry, only one copy ever gets into your app)
|
||||
- CSS files imported into the component are only loaded if the component is used
|
||||
- React components can be created as a class **or** as a function. In this function component, props are passed in as a function parameter.
|
||||
> Until recently, you could only access state in class-based components. But with the advent of [hooks](https://reactjs.org/docs/hooks-intro.html) you can create stateful function components.
|
||||
- Since this is a function, we don't have any methods, including `render()`. Just return your JSX as you would in the render function of a class-based component.
|
||||
- `props.children` contains anything passed between the opening and closing tags: `<Button>I'm in children</Button>`
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -2,14 +2,14 @@
|
|||
|
||||
## Demo
|
||||
|
||||
To start off our Todo application we are going to follow the steps outline in [Thinking in React](https://reactjs.org/docs/thinking-in-react.html). The first step of the process is to break our application into a component hierarchy. For this app, we're going to keep it simple and just use four parts.
|
||||
To start off our todo application we are going to follow the steps outlined in [Thinking in React](https://reactjs.org/docs/thinking-in-react.html). The first step of the process is to break our application into a component hierarchy. For this app, we're going to keep it simple and just use four parts.
|
||||
|
||||
- TodoHeader
|
||||
- TodoList
|
||||
-TodoListItem
|
||||
- TodoListItem
|
||||
- TodoFooter
|
||||
|
||||
We could go a lot deeping creating buttons, inputs and checkboxes, but this is a great place start. Often you'll want to start with a single large control, and then start breaking it up into smaller pieces.
|
||||
We could go a lot deeper into creating buttons, inputs and checkboxes, but this is a great place to start. Often you'll want to start with a single large control and then break it up into smaller pieces.
|
||||
|
||||
### TodoApp
|
||||
|
||||
|
@ -32,13 +32,13 @@ export class TodoApp extends React.Component {
|
|||
}
|
||||
```
|
||||
|
||||
We'll start off with all of the file scaffolded and imported into our App. This will let us dive right into each control and see updates quickly.
|
||||
We'll start off with all of the files scaffolded and imported into our App. This will let us dive right into each control and see updates quickly.
|
||||
|
||||
### TodoHeader
|
||||
|
||||
Our objective is to create a static version of our application, so we'll copy over the entire header tag, minus any function calls we may have added.
|
||||
Our objective for now is to create a static version of our application, so we'll copy over the entire header tag from a previous step, minus any function calls we added.
|
||||
|
||||
> Note that since this is React we had to change `class` to `className`, otherwise nothing changes
|
||||
> Note that since this is React we had to change `class` to `className`, but nothing else changes.
|
||||
|
||||
```jsx
|
||||
return (
|
||||
|
@ -59,7 +59,7 @@ return (
|
|||
|
||||
### TodoListItem
|
||||
|
||||
Anytime you see repeated complex elements, that is usually a sign to create a new component. With a few props you can typically abstract all of those elements into a single component. This is certainly the case with Todos items.
|
||||
Any time you see repeated complex elements, that is usually a sign that you should create a new component. With a few props you can typically abstract all of those elements into a single component. This is certainly the case with todo items.
|
||||
|
||||
```jsx
|
||||
return (
|
||||
|
@ -71,4 +71,4 @@ return (
|
|||
);
|
||||
```
|
||||
|
||||
> Note that I've removed the title span as it was only needed to make targeting that text easier
|
||||
> Note that I've removed the title span as it was only needed to make targeting that text easier.
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
# Creating a State Driven UI
|
||||
# Creating a State-Driven UI
|
||||
|
||||
In React data travels two directions, top down in the form of state propagating throughout controls, and bottom up, as interacting with the UI flows back up to modify the state. When writing an application it's often helpful to think of these two directions as separate parts of the development process.
|
||||
In React, data travels two directions: top-down in the form of state propagating throughout controls, and bottom-up as interacting with the UI flows back up to modify the state. When writing an application it's often helpful to think of these two directions as separate parts of the development process.
|
||||
|
||||
## demo
|
||||
## Demo
|
||||
|
||||
[Step #3 of Thinking in React](https://reactjs.org/docs/thinking-in-react.html) suggests finding the "minimal set of mutable state" that your application requires. So in this demo we are going to add that "minimal state" to our application and drive our UI off of that data. With that done the next step will be to create ways to modify that state, which will in turn cascade down through our UI. This [reconciliation](https://reactjs.org/docs/reconciliation.html) process, figuring out what in your UI needs to change based on changing state, is what React excels in.
|
||||
[Step #3 of "Thinking in React"](https://reactjs.org/docs/thinking-in-react.html) suggests finding the "minimal set of mutable state" that your application requires. So in this demo we are going to add that "minimal state" to our application and drive our UI off of that data. With that done, the next step will be to create ways to modify that state, which will in turn cascade down through our UI. This [reconciliation](https://reactjs.org/docs/reconciliation.html) process, figuring out what in your UI needs to change based on changing state, is what React excels at.
|
||||
|
||||
### Adding State to App
|
||||
|
||||
For our minimal state, we're going to include just two keys: `todos` and `filter`. We don't need to worry about a `remaining` value because we can calculate that by looking at the number of unchecked todos.
|
||||
|
||||
So here is our full constructor
|
||||
So here is our full constructor:
|
||||
|
||||
```jsx
|
||||
constructor(props) {
|
||||
|
@ -39,16 +39,18 @@ constructor(props) {
|
|||
}
|
||||
```
|
||||
|
||||
> You could use an array to represent your todos. Array manipulation can be easier in some cases, but this object approach simplifies other functionality and will ultimately be more performant.
|
||||
> You could also use an array to represent your todos. Array manipulation can be easier in some cases, but this object approach simplifies other functionality and will ultimately be more performant.
|
||||
|
||||
### Passing State Through to UI
|
||||
|
||||
To avoid reaching into state over and over, we once again use deconstruction to pull out the pieces we need.
|
||||
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
|
||||
|
@ -61,9 +63,9 @@ return (
|
|||
);
|
||||
```
|
||||
|
||||
### State Driven TodoList
|
||||
### State-Driven TodoList
|
||||
|
||||
I've already pulled out our props into `filter` and `todos` variables, and written a bit of JS that will return an array of filtered todo `id`s. We'll be using that filtered array to render out todo items.
|
||||
I've already pulled out our props into `filter` and `todos` variables, and written a bit of JS that will return an array of filtered todo `id`s. We'll be using that filtered array to render our todo items.
|
||||
|
||||
```jsx
|
||||
{
|
||||
|
@ -71,43 +73,51 @@ I've already pulled out our props into `filter` and `todos` variables, and writt
|
|||
}
|
||||
```
|
||||
|
||||
- **map**: A JavaScript map takes in an array (filteredTodos) and transforms it into a new array (our rendered TodoListItems)
|
||||
- **key**: We use the `id` from the `filterTodos` array as the [list key](https://reactjs.org/docs/lists-and-keys.html) to help React track each item as state changes.
|
||||
- **id**: The key is not actually passed into the component, so we pass the key in as `id` as well. This will help us out later.
|
||||
- **todos[id]**: Lastly we use the `id` to grab the todo from our `todos` object, then use the [spread operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) to pass through the todo's `label` and `completed` values.
|
||||
- [`map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map): This method transforms the array it's called on into a new array (our rendered TodoListItems).
|
||||
- `key`: We use the `id` from the `filterTodos` array as the [list item key](https://reactjs.org/docs/lists-and-keys.html) to help React track each item as state changes and the component re-renders.
|
||||
- `id`: The `key` is not actually passed into the component, so we pass the same value as `id` as well. This will help us out later.
|
||||
- `todos[id]`: Lastly we use the `id` to grab the todo from our `todos` object, then use the [spread operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) to pass through the todo's `label` and `completed` values.
|
||||
> This spread operator is the same as saying `label={todos[id].label} completed={todos[id].completed}`. Pretty obvious why spread is so handy!
|
||||
|
||||
### State Driven and Stateful Header
|
||||
### State-Driven and Stateful Header
|
||||
|
||||
Within the header we've got a situation where we not only want to pass `filter` state down to it, but we also want to maintain state within the control. Fortunately, this is no problem at all for React. First off let's deal with the incoming state.
|
||||
|
||||
#### Conditional ClassNames
|
||||
#### Conditional Class Names
|
||||
|
||||
In CSS based styling, visual states are applied by adding and removing classes. We can use the filter value to conditionally add a class thereby lighting up the correct filter button.
|
||||
In CSS-based styling, visual states are applied by adding and removing classes. We can use the filter value to conditionally add a class, thereby lighting up the correct filter button.
|
||||
|
||||
```jsx
|
||||
<nav className="filter">
|
||||
<button className={filter == 'all' ? 'selected' : ''}>all</button>
|
||||
<button className={filter == 'active' ? 'selected' : ''}>active</button>
|
||||
<button className={filter == 'completed' ? 'selected' : ''}>completed</button>
|
||||
<button className={filter === 'all' ? 'selected' : ''}>all</button>
|
||||
<button className={filter === 'active' ? 'selected' : ''}>active</button>
|
||||
<button className={filter === 'completed' ? 'selected' : ''}>completed</button>
|
||||
</nav>
|
||||
```
|
||||
|
||||
> Ternary operators are very popular in React code as each expression could be a string for a className, or even a JSX element.
|
||||
> The [ternary operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator) `condition ? expressionIfTrue : expressionIfFalse` is widely used in React code, as each expression could be a string for a className or even a JSX element.
|
||||
|
||||
#### Creating a Controlled Input
|
||||
#### Adding a Controlled Input
|
||||
|
||||
In tradition 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 whos 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.
|
||||
In React, form elements such as `<input>`, `<textarea>`, and `<select>` can be used as either **uncontrolled** or **controlled**. (This paradigm also applies to UI Fabric's customized implementations of form components, which we'll use later.)
|
||||
|
||||
To create a controlled component, we need two things, which our demo already provides:
|
||||
An **uncontrolled input** maintains its current value internally and updates it based on user interactions (entering text, choosing options, etc). The code only pulls the value from the input when it's needed, such as on submit. This is similar to how inputs in a plain HTML form work.
|
||||
|
||||
1. A state variable to hold the input's value
|
||||
A **controlled input** takes its current value from a prop and use a callback to notify the parent component of changes made by the user. The input's value doesn't change until the parent component updates the input's props in response to the callback.
|
||||
|
||||
Typically, a controlled input's current value is stored in the parent component's state (then passed to the input as a prop during render). The parent updates its state in response to the callback, which causes the input to be re-rendered with a new prop value. This round trip process might sound inefficient, but in reality it has little to no impact and helps enable some advanced form functionality.
|
||||
|
||||
> The distinction between controlled and uncontrolled is important to understand when writing or using form components, and misunderstandings of this concept are a very common source of bugs. See [this article](https://goshakkk.name/controlled-vs-uncontrolled-inputs-react/) for a more detailed explanation.
|
||||
|
||||
To add a controlled input, we need two things, which our demo already provides:
|
||||
|
||||
1. A state variable to hold the input's value:
|
||||
|
||||
```jsx
|
||||
this.state = { labelInput: '' };
|
||||
```
|
||||
|
||||
2. A function to update that value
|
||||
2. A callback function to update that value:
|
||||
|
||||
```jsx
|
||||
_onChange = evt => {
|
||||
|
@ -120,3 +130,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 />
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
### TodoFooter
|
||||
|
||||
1. Use the provided `itemCount` value drive the number of items left.
|
||||
2. Use a ternary operator to print `item` vs `items` based on if `itemCount === 1`
|
||||
1. Use the provided `itemCount` value to display the current number of items left.
|
||||
2. Use a ternary operator to print "item" vs "item**s**" based on whether `itemCount === 1`.
|
||||
|
||||
### TodoListItem
|
||||
|
||||
1. Pull in `label` and `completed` from props using [destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring)
|
||||
2. Set the todo's text to `label` and the `checked` prop to `completed`
|
||||
> Note that this is only half the work we need to do to make these controlled inputs. What is the other half?
|
||||
> Note that this is only half the work we need to do to make these controlled inputs work. What is the other half?
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -17,9 +17,9 @@ export class TodoHeader extends React.Component<any, any> {
|
|||
<button className="submit">Add</button>
|
||||
</div>
|
||||
<nav className="filter">
|
||||
<button className={filter == 'all' ? 'selected' : ''}>all</button>
|
||||
<button className={filter == 'active' ? 'selected' : ''}>active</button>
|
||||
<button className={filter == 'completed' ? 'selected' : ''}>completed</button>
|
||||
<button className={filter === 'all' ? 'selected' : ''}>all</button>
|
||||
<button className={filter === 'active' ? 'selected' : ''}>active</button>
|
||||
<button className={filter === 'completed' ? 'selected' : ''}>completed</button>
|
||||
</nav>
|
||||
</header>
|
||||
);
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -17,9 +17,9 @@ export class TodoHeader extends React.Component<any, any> {
|
|||
<button className="submit">Add</button>
|
||||
</div>
|
||||
<nav className="filter">
|
||||
<button className={filter == 'all' ? 'selected' : ''}>all</button>
|
||||
<button className={filter == 'active' ? 'selected' : ''}>active</button>
|
||||
<button className={filter == 'completed' ? 'selected' : ''}>completed</button>
|
||||
<button className={filter === 'all' ? 'selected' : ''}>all</button>
|
||||
<button className={filter === 'active' ? 'selected' : ''}>active</button>
|
||||
<button className={filter === 'completed' ? 'selected' : ''}>completed</button>
|
||||
</nav>
|
||||
</header>
|
||||
);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -3,10 +3,11 @@ import React from 'react';
|
|||
export class TodoListItem extends React.Component<any, any> {
|
||||
render() {
|
||||
const { label, completed } = this.props;
|
||||
// The "no-op" onChange handler prevents a console warning from React at runtime
|
||||
return (
|
||||
<li className="todo">
|
||||
<label>
|
||||
<input type="checkbox" checked={completed} /> {label}
|
||||
<input type="checkbox" checked={completed} onChange={() => undefined} /> {label}
|
||||
</label>
|
||||
</li>
|
||||
);
|
||||
|
|
|
@ -1,34 +1,34 @@
|
|||
# Types and Creating a UI Driven State
|
||||
|
||||
Now that we have a UI that is purely driven by the state of our app, we need to add functionality to allow the UI to drive the state. This is often done by creating functions that call `setState` like we saw in the `todoHeader`, that are passed down to the UI as props.
|
||||
Now that we have a UI that is purely driven by the state of our app, we need to add functionality to allow the UI to drive the state. This is often done by creating functions that call `setState` like we saw in the `TodoHeader`. Values from the state are then passed down to the UI as props.
|
||||
|
||||
> We'll be learning in part 2 of this workshop how we can expose these functions without explicitly passing them down via props
|
||||
> We'll be learning in part 2 of this workshop how we can expose these functions without explicitly passing them down via props.
|
||||
|
||||
This is our core 'business logic' and handles everything our basic 'CRUD' operations of "Create, Read, Update, Delete". We don't have time to walk through writing all of those functions, but you can see that they are already provided in the demo's `TodoApp` and passed into our components.
|
||||
This is our core "business logic" and handles everything our basic "CRUD" operations: Create, Read, Update, Delete. We don't have time to walk through writing all of those functions, but you can see that they are already provided in the demo's `TodoApp` and passed into our components.
|
||||
|
||||
## Intro to TypeScript
|
||||
|
||||
Taking a look at our components in `TodoApp` you can see that our list of props is not just getting longer, but is getting much more complex! We're passing through functions with various signatures, complex `todos` objects as well as filter strings which are always one of three values.
|
||||
Taking a look at our components in `TodoApp`, you can see that our list of props is not just getting longer, but is getting much more complex! We're passing through functions with various signatures, complex `todos` objects, and filter strings which are always one of three values.
|
||||
|
||||
As applications grow, it becomes increasing difficult to remember what each function does, or what each todo contains. Also, as JavaScript is a loosely type language, if I wanted to change the value of `todos` to an array inside my `TodoList`, JavaScript wouldn't care. But if `TodoListItems` was expecting an object, our application would break.
|
||||
As applications grow, it becomes increasing difficult to remember what each function does, or what each todo contains. Also, as JavaScript is a loosely typed language, if I wanted to change the value of `todos` to an array inside my `TodoList`, JavaScript wouldn't care. But if `TodoListItems` was expecting an object, our application would break.
|
||||
|
||||
It is because of these two reasons that the entire industry is shifting to writing applications that are strongly typed, and are using TypeScript to accomplish that.
|
||||
It for these two reasons that the entire industry is shifting to writing applications that are strongly typed, and many are using TypeScript to accomplish that.
|
||||
|
||||
As [their website](https://www.typescriptlang.org/) state:
|
||||
As [TypeScript's website](https://www.typescriptlang.org/) states:
|
||||
|
||||
> TypeScript is a superset of JavaScript that compiles to plain JavaScript
|
||||
> TypeScript is a superset of JavaScript that compiles to plain JavaScript.
|
||||
|
||||
If you've ever used [Sass](https://sass-lang.com/) you are familiar with this concept. In the same say that all valid CSS is valid Sass, all valid JavaScript is valid TypeScript. That's why most of this project has been writing in `ts` and `tsx` files instead of `js` and `jsx` files.
|
||||
If you've ever used [Sass](https://sass-lang.com/) you are familiar with this concept. In the same way that all valid CSS is valid Sass, all valid JavaScript is valid TypeScript. That's why most of these exercises have been written in `ts` and `tsx` files instead of `js` and `jsx` files.
|
||||
|
||||
Let's dive into the demo and see how TypeScript can help us better understand our component props, and guard against future regressions.
|
||||
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.
|
||||
Let's start off in the TodoList, as that has the most data flow up and down. There isn't any interactive UI in this component, as we're simply passing `completed` down to each `TodoListItem`, but we can write a props interface for the component 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`.
|
||||
Looking at our `TodoApp` we know that `TodoList` has three props: `filter`, `todos`, and `complete`. We'll start by creating an interface called `TodoListProps` that represents this component's props.
|
||||
|
||||
```tsx
|
||||
interface TodoListProps {
|
||||
|
@ -38,7 +38,7 @@ interface TodoListProps {
|
|||
}
|
||||
```
|
||||
|
||||
> Note that we're using the `any` keyword for now. This won't give us any type safety, but it does innumerate the valid props we can pass to this component.
|
||||
> Note that we're using the `any` keyword for now. This won't give us any type safety, but it does let us specify valid prop names we can pass to this component.
|
||||
|
||||
With that interface written, we'll add it to our component class.
|
||||
|
||||
|
@ -46,17 +46,17 @@ With that interface written, we'll add it to our component class.
|
|||
export class TodoList extends React.Component<TodoListProps, any>
|
||||
```
|
||||
|
||||
> Note that the first value in `<>` is for a props interface, and the second for state
|
||||
> Note that the first value in `<>` is for a props interface, and the second is for state.
|
||||
|
||||
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.
|
||||
So far we've only established what our prop names are, not the type of 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:
|
||||
We know that `filter` shouldn't be an object, array or function, so we can specify it should always be a string like this:
|
||||
|
||||
```tsx
|
||||
interface TodoListProps {
|
||||
|
@ -66,7 +66,7 @@ interface TodoListProps {
|
|||
}
|
||||
```
|
||||
|
||||
But since we know that the filter can be only one of three values, we can explicitly write it that way with [union types](https://www.typescriptlang.org/docs/handbook/advanced-types.html#union-types):
|
||||
But since we know that the filter can be only one of three values, we can make that explicit with a [union type](https://www.typescriptlang.org/docs/handbook/advanced-types.html#union-types):
|
||||
|
||||
```tsx
|
||||
interface TodoListProps {
|
||||
|
@ -76,11 +76,11 @@ interface TodoListProps {
|
|||
}
|
||||
```
|
||||
|
||||
Now try going back to `TodoApp` and changing the `filter` attribute in `TodoList` to something else.
|
||||
Now try going back to `TodoApp` and changing the `filter` attribute in `TodoList` to something else. You'll see an error in the editor (if using VS Code) and on the command line when you save the file.
|
||||
|
||||
#### 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.
|
||||
The `complete` prop isn't data, but a function. Fortunately, TypeScript can handle function types just as well as data.
|
||||
|
||||
```tsx
|
||||
interface TodoListProps {
|
||||
|
@ -90,9 +90,11 @@ 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.
|
||||
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 returned value.
|
||||
|
||||
### Todos Type
|
||||
> Technically, all functions in JavaScript return `undefined` if no other return value is specified, but declaring a return type of `void` causes TypeScript to error if you try to return a value from the function (or use its default returned value of `undefined`).
|
||||
|
||||
## 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.
|
||||
|
||||
|
@ -109,13 +111,13 @@ interface TodoListProps {
|
|||
}
|
||||
```
|
||||
|
||||
> Note that the `[]` notation does not mean an array, it is a [computed property](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Computed_property_names) notation.
|
||||
> Note that `[id: string]` does not indicate an array; it is an object [index signature](https://www.typescriptlang.org/docs/handbook/interfaces.html#indexable-types).
|
||||
|
||||
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.
|
||||
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. Compare this to plain JavaScript: if you had a typo in that line, you wouldn't understand why your filter wasn't working.
|
||||
|
||||
### Abstracting types
|
||||
## Sharing 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.
|
||||
Most of our components will need to specify types for `todos` and `filter`, so it's a good thing that TypeScript allows us to share types between files. I've already written up and exported those shared types in the file `TodoApp.types.ts`, so we just need to import them and use them in our interface.
|
||||
|
||||
```tsx
|
||||
import { FilterTypes, Todos } from '../TodoApp.types';
|
||||
|
@ -127,21 +129,21 @@ 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.
|
||||
|
||||
I've already imported `Todos`, and `FilterTypes` into the `TodoApp`, so we just need to add them to our class. We can even skip the 'interface', if we want to, and add them directly to the class.
|
||||
I've already imported `Todos` and `FilterTypes` into the `TodoApp`, so we just need to add them to our class. If we want, we can even skip a separate interface definition and just declare the type inline. (This is not recommended for types of any complexity or types that are used in multiple places.)
|
||||
|
||||
```tsx
|
||||
export class TodoApp extends React.Component<{}, { todos: Todos; filter: FilterTypes }>
|
||||
```
|
||||
|
||||
> Note that the first value in `<>` always refers to props. Since `TodoApp` takes none, we'll set it to an empty object.
|
||||
> Note that the first value in `<>` always refers to props. Since `TodoApp` takes none, we'll set it to an empty object type.
|
||||
|
||||
### 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.
|
||||
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 we can make `TodoListItemProps` reuse the `TodoItem` interface by extending it.
|
||||
|
||||
```tsx
|
||||
interface TodoListItemProps extends TodoItem {
|
||||
|
@ -150,17 +152,17 @@ interface TodoListItemProps extends TodoItem {
|
|||
}
|
||||
```
|
||||
|
||||
The end result of this is an interface with all 4 properties, `id`, `complete`, `completed` and `label`.
|
||||
The end result of this is an interface with all four properties: `id`, `complete`, `completed` and `label`.
|
||||
|
||||
Next we can pull in the remaining props:
|
||||
Next we can pull in the remaining props in the render function:
|
||||
|
||||
```jsx
|
||||
const { label, completed, complete, id } = this.props;
|
||||
```
|
||||
|
||||
And then use the input's `onChange` event to fire our `complete` callback. We can see in the signature that we expect and `id` of type string, so we'll pass our `id` prop in.
|
||||
And then use the input's `onChange` event to fire our `complete` callback. We can see in the signature that `complete` expects an `id` of type string, so we'll pass our `id` prop in.
|
||||
|
||||
> A [callback function](https://developer.mozilla.org/en-US/docs/Glossary/Callback_function) is a function passed into a component as a prop
|
||||
> A [callback function](https://developer.mozilla.org/en-US/docs/Glossary/Callback_function) is a function passed into a component as a prop.
|
||||
|
||||
```tsx
|
||||
<input type="checkbox" checked={completed} onChange={() => complete(id)} />
|
||||
|
@ -168,4 +170,4 @@ And then use the input's `onChange` event to fire our `complete` callback. We ca
|
|||
|
||||
> Note that the function param and prop name just happen to be the same. This isn't required.
|
||||
|
||||
Now that our todos are firing the `onChange` callback, give them a click and take look at how the app response. Since our footer text is driven off of the number of unchecked todos, the footer will automatically update to reflect the new state.
|
||||
Now that our todos are firing the `onChange` callback, give them a click and take look at how the app responds. Since our footer text is based on the number of unchecked todos, the footer will automatically update to reflect the new state.
|
||||
|
|
|
@ -6,7 +6,7 @@ export const TodoFooter = (props: any) => {
|
|||
return (
|
||||
<footer>
|
||||
<span>
|
||||
{itemCount} item{itemCount > 1 ? 's' : ''} left
|
||||
{itemCount} item{itemCount === 1 ? '' : 's'} left
|
||||
</span>
|
||||
<button className="submit">Clear Completed</button>
|
||||
</footer>
|
||||
|
|
|
@ -17,9 +17,9 @@ export class TodoHeader extends React.Component<any, any> {
|
|||
<button className="submit">Add</button>
|
||||
</div>
|
||||
<nav className="filter">
|
||||
<button className={filter == 'all' ? 'selected' : ''}>all</button>
|
||||
<button className={filter == 'active' ? 'selected' : ''}>active</button>
|
||||
<button className={filter == 'completed' ? 'selected' : ''}>completed</button>
|
||||
<button className={filter === 'all' ? 'selected' : ''}>all</button>
|
||||
<button className={filter === 'active' ? 'selected' : ''}>active</button>
|
||||
<button className={filter === 'completed' ? 'selected' : ''}>completed</button>
|
||||
</nav>
|
||||
</header>
|
||||
);
|
||||
|
|
|
@ -8,7 +8,7 @@ export class TodoListItem extends React.Component<any, any> {
|
|||
return (
|
||||
<li className="todo">
|
||||
<label>
|
||||
<input type="checkbox" checked={completed} /> {label}
|
||||
<input type="checkbox" checked={completed} onChange={() => undefined} /> {label}
|
||||
</label>
|
||||
</li>
|
||||
);
|
||||
|
|
|
@ -2,19 +2,26 @@
|
|||
|
||||
### TodoFooter
|
||||
|
||||
1. Open TodoFooter and write a TodoFooterProps interface. It should include two values, a function and an object. Assign this interface to props like this: `(props: TodoFooterProps)`
|
||||
1. Open TodoFooter and write a TodoFooterProps interface. It should include two values, a function and an object. Use this interface in the function props like this: `(props: TodoFooterProps)`
|
||||
|
||||
2. Write an `_onClick` function that calls `props.clear`.
|
||||
> Since TodoFooter is not a class the `_onClick` needs to be declared as a const, and placed before the `return`.
|
||||
3. Add `_onClick` to the button's `onClick`. You won't need to use `this` since this isn't a class.
|
||||
> We can't assign our `clear` function directly to `onClick`. We always need to create a function that calls our callbacks. `() => props.clear()`
|
||||
4. Test out this functionality. Check a few todos complete and click the `Clear Completed` button
|
||||
> Since TodoFooter is not a class, the `_onClick` function needs to be stored in a const placed before the `return`.
|
||||
|
||||
3. Assign `_onClick` to the button's `onClick` prop. You won't need to use `this` since the component isn't a class.
|
||||
|
||||
4. Test out this functionality. Check a few todos complete and click the `Clear Completed` button.
|
||||
|
||||
### TodoHeader
|
||||
|
||||
1. Open TodoHeader and write TodoHeaderProps which will include 3 values. Replace the first `any` with this interface.
|
||||
2. This component also has state. Write TodoHeaderState (there's just one item), and add this where the second `any` was.
|
||||
1. Open TodoHeader and write TodoHeaderProps which will include three values. Replace the first `any` in the class declaration with this interface.
|
||||
|
||||
2. This component also has state. Write TodoHeaderState (there's just one value), and add this where the second `any` was.
|
||||
|
||||
3. Add `_onFilter` to each of the filter buttons
|
||||
> Note that we can't add new parameters to onClick, but we can pull information from the event target!
|
||||
|
||||
4. Write an `_onAdd` method that calls `addTodo` on the current `labelInput`, then sets the `labelInput` in state to an empty string
|
||||
|
||||
5. Call `_onAdd` from the submit button
|
||||
|
||||
6. Check out this new functionality! We can now add and filter todos!
|
||||
|
|
|
@ -6,7 +6,7 @@ export const TodoFooter = props => {
|
|||
return (
|
||||
<footer>
|
||||
<span>
|
||||
{itemCount} item{itemCount > 1 ? 's' : ''} left
|
||||
{itemCount} item{itemCount === 1 ? '' : 's'} left
|
||||
</span>
|
||||
<button className="submit">Clear Completed</button>
|
||||
</footer>
|
||||
|
|
|
@ -17,9 +17,9 @@ export class TodoHeader extends React.Component<any, any> {
|
|||
<button className="submit">Add</button>
|
||||
</div>
|
||||
<nav className="filter">
|
||||
<button className={filter == 'all' ? 'selected' : ''}>all</button>
|
||||
<button className={filter == 'active' ? 'selected' : ''}>active</button>
|
||||
<button className={filter == 'completed' ? 'selected' : ''}>completed</button>
|
||||
<button className={filter === 'all' ? 'selected' : ''}>all</button>
|
||||
<button className={filter === 'active' ? 'selected' : ''}>active</button>
|
||||
<button className={filter === 'completed' ? 'selected' : ''}>completed</button>
|
||||
</nav>
|
||||
</header>
|
||||
);
|
||||
|
|
|
@ -14,7 +14,7 @@ export const TodoFooter = (props: TodoFooterProps) => {
|
|||
return (
|
||||
<footer>
|
||||
<span>
|
||||
{itemCount} item{itemCount > 1 ? 's' : ''} left
|
||||
{itemCount} item{itemCount === 1 ? '' : 's'} left
|
||||
</span>
|
||||
<button onClick={_onClick} className="submit">
|
||||
Clear Completed
|
||||
|
|
|
@ -29,13 +29,13 @@ export class TodoHeader extends React.Component<TodoHeaderProps, TodoHeaderState
|
|||
</button>
|
||||
</div>
|
||||
<nav className="filter">
|
||||
<button onClick={this._onFilter} className={filter == 'all' ? 'selected' : ''}>
|
||||
<button onClick={this._onFilter} className={filter === 'all' ? 'selected' : ''}>
|
||||
all
|
||||
</button>
|
||||
<button onClick={this._onFilter} className={filter == 'active' ? 'selected' : ''}>
|
||||
<button onClick={this._onFilter} className={filter === 'active' ? 'selected' : ''}>
|
||||
active
|
||||
</button>
|
||||
<button onClick={this._onFilter} className={filter == 'completed' ? 'selected' : ''}>
|
||||
<button onClick={this._onFilter} className={filter === 'completed' ? 'selected' : ''}>
|
||||
completed
|
||||
</button>
|
||||
</nav>
|
||||
|
|
|
@ -2,56 +2,83 @@
|
|||
|
||||
[Lessons](../) | [Exercise](./exercise/) | [Demo](./demo/)
|
||||
|
||||
In this step, we'll cover enough of the TypeScript concepts to be productive with the React & Redux frameworks.
|
||||
In this step, we'll cover enough TypeScript concepts to be productive with the React & Redux frameworks.
|
||||
|
||||
Topics in this step will include:
|
||||
|
||||
- ES modules
|
||||
- Basic Types
|
||||
- Interfaces & Classes
|
||||
- Basic Generics
|
||||
- Spread and Destructuring
|
||||
- Async / Await
|
||||
- [ES modules](#modules)
|
||||
- [Types](#typescript-types)
|
||||
- [Spread](#spread-operator) and [Destructuring](#destructuring)
|
||||
- [Promise](#promise) and [Async / Await](#async--await)
|
||||
|
||||
> To try out TypeScript concepts and see the corresponding JavaScript, you can use the [TypeScript playground](http://www.typescriptlang.org/play/index.html). We won't be using it in this training, but it's very handy in general!
|
||||
|
||||
## Modules
|
||||
|
||||
Historically, JS is only executed in browser. The code all had to be loaded from `<script>` tags. Since the introduction of node.js, the JS community needed a way to scale beyond just single script files. Other language support the notion of modules. There are many JS modularity standards today.
|
||||
Historically, JS was only executed in-browser. The code all had to be loaded using `<script>` tags. With the introduction of node.js, the JS community needed a way to scale beyond just single script files. Other languages support the notion of modules, so various groups started developing modularity standards for JS.
|
||||
|
||||
The most important ones to know about are:
|
||||
|
||||
- commonjs - Node.js's standard to support modules
|
||||
- synchronous
|
||||
- require() function, can be dynamically called in the course of a program
|
||||
- ESM (ECMAScript module) - language level support
|
||||
- **commonjs** - Node.js's standard to support modules
|
||||
- synchronous loading using `require()` function
|
||||
- `require()` can be dynamically called in the course of a program
|
||||
- **ESM (ECMAScript module)** - language-level support
|
||||
- statically analyzable and synchronous
|
||||
- dynamic and asynchronous support via `import()` that returns a promise
|
||||
|
||||
> For more information about the *many* modularity patterns and standards developed over time, see [this article](https://medium.freecodecamp.org/javascript-modules-a-beginner-s-guide-783f7d7a5fcc). You may still encounter some of the older patterns in legacy code.
|
||||
|
||||
## TypeScript Types
|
||||
|
||||
Refer to the `demo/src` for some examples of some of the types available in TS that benefits a React developer.
|
||||
Refer to [`demo/src`](./demo/src) for examples of some of the types available in TS that benefit a React developer.
|
||||
|
||||
## Spread Syntax
|
||||
## Spread Operator
|
||||
|
||||
Spread syntax allows for quick way to clone and concatenate objects and arrays. This syntax is seen a lot inside React props and Redux reducers.
|
||||
The spread operator `...` provides a quick way to clone and concatenate objects and arrays. This syntax is seen a lot inside React props and Redux reducers.
|
||||
|
||||
To shallow copy something:
|
||||
With objects:
|
||||
|
||||
```ts
|
||||
const cloned = { ...obj };
|
||||
```
|
||||
// Shallow copy an object
|
||||
const cloned1 = { ...obj };
|
||||
|
||||
To shallow copy and add / overwrite a key:
|
||||
// Shallow copy and add/overwrite a key
|
||||
const overridden1 = { ...obj, key: value };
|
||||
|
||||
```ts
|
||||
const overridden = { ...obj, key: value };
|
||||
```
|
||||
// Shallow copy multiple objects and add a key
|
||||
const cloned2 = { ...obj1, ...obj2, key: value };
|
||||
|
||||
You can have an expression to calculate this key if it is dynamic:
|
||||
|
||||
```ts
|
||||
// Use an expression to calculate a key dynamically
|
||||
const overridden = { ...object, [key + '-suffix']: value };
|
||||
```
|
||||
|
||||
With arrays:
|
||||
|
||||
```ts
|
||||
const copy1 = [...arr];
|
||||
const copy2 = [...arr1, ...arr2];
|
||||
const copyWithExtras = [123, ...arr, 'hello'];
|
||||
```
|
||||
|
||||
Spreading an array into positional arguments to a function:
|
||||
|
||||
```ts
|
||||
function myFunc(a: number, b: number, c: number) {
|
||||
// ...
|
||||
}
|
||||
const arr = [1, 2, 3];
|
||||
myFunc(...arr);
|
||||
```
|
||||
|
||||
Spreading an object into props for a React component:
|
||||
|
||||
```tsx
|
||||
const obj = { a: 1, b: 2, c: 3 };
|
||||
// equivalent to:
|
||||
// <MyComponent a={obj.a} b={obj.b} c={obj.c} />
|
||||
const rendered = <MyComponent {...obj} />;
|
||||
```
|
||||
|
||||
## Destructuring
|
||||
|
||||
Destructuring is a concise way to take properties out of an object or array:
|
||||
|
@ -60,32 +87,31 @@ Destructuring is a concise way to take properties out of an object or array:
|
|||
const obj = { foo: 1, bar: 2 };
|
||||
const { foo, bar } = obj;
|
||||
// foo = 1, bar = 2
|
||||
```
|
||||
|
||||
Same thing for array:
|
||||
|
||||
```ts
|
||||
const arr = [1, 2];
|
||||
const [foo, bar] = arr;
|
||||
// foo = 1, bar = 2
|
||||
```
|
||||
|
||||
You can separate an item and the rest of the object with destructuring:
|
||||
You can separate an item from the rest of the object with destructuring:
|
||||
|
||||
```ts
|
||||
const obj = { a: 1, b: 2, c: 3, d: 4 };
|
||||
const { a, ...rest } = obj;
|
||||
// a = 1, rest = {b: 2, c: 3, d: 4}
|
||||
|
||||
const arr = [1, 2, 3];
|
||||
const [foo, ...bar] = arr;
|
||||
// foo = 1, bar = [2, 3]
|
||||
```
|
||||
|
||||
# Promise
|
||||
## Promise
|
||||
|
||||
A promise is an object that represent work that will be completed later, asynchronously. It is a chainable so writing async code is maintainable. Typically legacy async code uses callback to let the caller have control over what to do after the task has been completed.
|
||||
A promise is an object representing work that will be completed later, asynchronously. Promises are chainable, which helps with writing maintainable async code. (Typically, legacy async code uses callbacks to let the caller have control over what to do after the task has been completed, which becomes very hard to read.)
|
||||
|
||||
```ts
|
||||
const aPromise = new Promise((resolve, reject) => {
|
||||
// do something async and call resolve() to let promise know it is done
|
||||
|
||||
setTimeout(() => {
|
||||
// setTimeout will call this method after 1s, simulating async operation like network calls
|
||||
resolve();
|
||||
|
@ -93,10 +119,11 @@ const aPromise = new Promise((resolve, reject) => {
|
|||
});
|
||||
```
|
||||
|
||||
The promise object exposes a `then()` function that is chainable. `catch()` is present that catches all exceptions or `reject()` calls:
|
||||
Each promise instance exposes a `then()` function that is chainable. It also provides `catch()`, which catches all exceptions or `reject()` calls:
|
||||
|
||||
```ts
|
||||
const aPromise = Promise.resolve('hello world'); /* ... just an example promise */
|
||||
// Promise.resolve() creates an already-resolved promise instance
|
||||
const aPromise = Promise.resolve('hello world');
|
||||
|
||||
aPromise
|
||||
.then(result => {
|
||||
|
@ -110,9 +137,11 @@ aPromise
|
|||
});
|
||||
```
|
||||
|
||||
# Async / Await
|
||||
> For more information, see [this overview of promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises) or [this deep dive](https://developers.google.com/web/fundamentals/primers/promises).
|
||||
|
||||
This syntax is inspired heavily by C#'s async / await syntax. To write an async function write it like this:
|
||||
## Async / Await
|
||||
|
||||
This syntax is inspired heavily by C#'s async / await syntax. An async function is written like this:
|
||||
|
||||
```ts
|
||||
async function someFunctionAsync() {
|
||||
|
@ -122,7 +151,7 @@ async function someFunctionAsync() {
|
|||
}
|
||||
```
|
||||
|
||||
All functions that are marked `async` return a `Promise` automatically. This previous example returned a `Promise<string>`, and can be used like this:
|
||||
All functions that are marked `async` return a `Promise` automatically. The previous example returned a `Promise<string>`, and can be used like this:
|
||||
|
||||
```ts
|
||||
someFunctionAsync().then(result => {
|
||||
|
@ -130,27 +159,29 @@ someFunctionAsync().then(result => {
|
|||
});
|
||||
```
|
||||
|
||||
> For more information, see [this article](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function).
|
||||
|
||||
# Exercise
|
||||
|
||||
Please complete all exercises inside the `exercise/src` folder unless otherwise specified in the exercises below. First, open up [Step2-01 exercise page](http://localhost:8080/step2-01/exercise/) to see the results while you're implementing things.
|
||||
Exercises will be completed under this step's `exercise/src` folder unless otherwise noted. You'll also want to open the [Step2-01 exercise page](http://localhost:8080/step2-01/exercise/) to see the results as you work.
|
||||
|
||||
## Modules
|
||||
|
||||
1. Open up file called `index.ts` in VS Code
|
||||
1. Open the file `exercise/src/index.ts` in VS Code
|
||||
|
||||
2. Create another module file called `fibonacci.ts`
|
||||
|
||||
3. Inside the file from (step 2), write a function called `fib(n)` that takes in a number and returns a the n-th Fibonacci number - be sure the specify the type of n
|
||||
3. Inside the file from (step 2), write a function called `fib(n)` that takes in a number and returns the `n`-th Fibonacci number (be sure the specify the type of `n`).
|
||||
|
||||
> HINT: function fib(n: number) { return n <= 1 ? n : fib(n - 1) + fib(n - 2); }
|
||||
> HINT: `function fib(n: number) { return n <= 1 ? n : fib(n - 1) + fib(n - 2); }`
|
||||
|
||||
4. Export `fib(n)` as a **named export**
|
||||
|
||||
5. Export another const variable as a **default export**
|
||||
|
||||
6. Inside `index.ts` Import both the modules created in steps (4) and (5) and use the built-in `console.log()` function to log the result of `fib(FibConst)`.
|
||||
6. Inside `index.ts`, import both of the modules created in steps (4) and (5) and use the built-in `console.log()` function to log the result of `fib(FibConst)`.
|
||||
|
||||
## Types, Interfaces, and Classes
|
||||
## Types and Interfaces
|
||||
|
||||
Inside `index.ts`:
|
||||
|
||||
|
@ -158,15 +189,15 @@ Inside `index.ts`:
|
|||
|
||||
2. Describe a type of car with an interface: `interface Car { ... }` complete with `wheels`, `color`, `make`, `model`
|
||||
|
||||
## Generic
|
||||
## Generics
|
||||
|
||||
Inside `stack.ts`, create a generic class for a `Stack<T>` complete with a typed `pop()` and `push()` methods
|
||||
Inside `stack.ts`, create a generic class for a `Stack<T>` complete with a typed `pop()` and `push()` methods.
|
||||
|
||||
> Hint: the JavaScript array already has `push()` and `pop()` implemented for you. That can be your backing store.
|
||||
|
||||
Be sure to use the built-in `console.log()` to show the functionality of `Stack<T>`
|
||||
Be sure to use the built-in `console.log()` to show the functionality of `Stack<T>`.
|
||||
|
||||
## Spread and Destructure
|
||||
## Spread and Destructuring
|
||||
|
||||
1. Note the following code in index.ts:
|
||||
|
||||
|
@ -187,11 +218,11 @@ const obj2 = {
|
|||
|
||||
2. Now create a one-liner using the spread syntax `{...x, ...y}` to create a new variable that combines these two objects.
|
||||
|
||||
3. Using the destructuring syntax to retrieve the values for `{first, second, catcher}` from this new object created in step (2).
|
||||
3. Use the destructuring syntax to retrieve the values for `{first, second, catcher}` from the new object created in step (2).
|
||||
|
||||
## Async / Await
|
||||
|
||||
1. Note the following code in index.ts:
|
||||
Note the following code in index.ts:
|
||||
|
||||
```ts
|
||||
function makePromise() {
|
||||
|
@ -199,6 +230,6 @@ function makePromise() {
|
|||
}
|
||||
```
|
||||
|
||||
2. call `makePromise()` with the `await` syntax and log the results using the provided `log()` function
|
||||
1. Call `makePromise()` with the `await` syntax and log the results using the provided `log()` function.
|
||||
|
||||
3. create a new function that uses the `async` keyword to create an async function. Make an await call to `makePromise()` and return the results
|
||||
2. Create a new function that uses the `async` keyword. Make an `await` call to `makePromise()` and return the results.
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<body class="ms-Fabric">
|
||||
<div id="markdownReadme"></div>
|
||||
<div id="app">
|
||||
Nothing to show here, just look at your console window for output. Hit F12 to open console window.
|
||||
Nothing to show here; just look at your console window for output. Hit F12 (<code>cmd+option+I</code> on Mac) to open console window.
|
||||
</div>
|
||||
<script src="../../assets/scripts.js"></script>
|
||||
</body>
|
||||
|
|
|
@ -3,10 +3,10 @@ async function fetchSomething() {
|
|||
return await response.text();
|
||||
}
|
||||
|
||||
// Async functions always returns Promise
|
||||
// Async functions always return a Promise
|
||||
fetchSomething().then(text => {
|
||||
console.log('hello ' + text);
|
||||
});
|
||||
|
||||
// adding an export to turn this into a "module"
|
||||
// adding an export turns this into a "module"
|
||||
export default {};
|
||||
|
|
|
@ -19,5 +19,5 @@ function reverse<T>(arg: T[]): T[] {
|
|||
return arg;
|
||||
}
|
||||
|
||||
// adding an export to turn this into a "module"
|
||||
// adding an export turns this into a "module"
|
||||
export default {};
|
||||
|
|
|
@ -1,22 +1,32 @@
|
|||
// Interface for an object or class
|
||||
interface Car {
|
||||
make: string;
|
||||
model: string;
|
||||
}
|
||||
|
||||
class MyCar implements Car {
|
||||
make: 'Honda';
|
||||
model: 'Accord';
|
||||
}
|
||||
make: string;
|
||||
model: string;
|
||||
|
||||
const myCar: Car = {
|
||||
constructor(make: string, model: string) {
|
||||
this.make = make;
|
||||
this.model = model;
|
||||
}
|
||||
}
|
||||
const myCar1: Car = new MyCar('Honda', 'Accord');
|
||||
|
||||
const myCar2: Car = {
|
||||
make: 'Honda',
|
||||
model: 'Accord'
|
||||
};
|
||||
|
||||
// Interface as Functions
|
||||
// Interface for a function
|
||||
interface InterestingFn {
|
||||
(someArgs: string): number;
|
||||
}
|
||||
const interesting: InterestingFn = (someArgs: string): number => {
|
||||
return Number(someArgs);
|
||||
};
|
||||
|
||||
// adding an export to turn this into a "module"
|
||||
export default {};
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
// These are named imports from a file relative to this file
|
||||
import { namedConst, namedFn, namedObj, namedConstBracket } from './named';
|
||||
|
||||
// We can even apply an alias to the named constant
|
||||
// We can even give an alias to the named constant
|
||||
import { namedConst as c } from './named';
|
||||
|
||||
// These are the same instances of the named imports, but gets imported all at the same time under a single object
|
||||
// These are the *same instances* of the named imports, but they all get imported inside a single object
|
||||
import * as named from './named';
|
||||
|
||||
// Print out the exports
|
||||
|
@ -14,12 +14,15 @@ console.log(namedFn());
|
|||
console.log(namedObj);
|
||||
console.log(namedConstBracket);
|
||||
|
||||
// Print out exports through module level import
|
||||
// Print out exports from module level import
|
||||
console.log(named.namedConst);
|
||||
console.log(named.namedFn());
|
||||
console.log(named.namedObj);
|
||||
console.log(named.namedConstBracket);
|
||||
|
||||
// The named and module-level imports reference the same object instances
|
||||
console.log(namedObj === named.namedObj); // true
|
||||
|
||||
// Default import can be named anything we want as the consumer
|
||||
import DefaultClass from './default';
|
||||
import Foo from './default';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Destructuring
|
||||
var [a, b, ...rest] = [1, 2, 3, 4];
|
||||
console.log(a, b, rest); // 1,2,[3,4]
|
||||
console.log(a, b, rest); // 1 2 [3,4]
|
||||
|
||||
// Array assignment
|
||||
var list = [1, 2];
|
||||
|
@ -20,5 +20,5 @@ const obj3 = { ...obj1, ...obj2 };
|
|||
// Destructuring object
|
||||
const { x } = obj3;
|
||||
|
||||
// adding an export to turn this into a "module"
|
||||
// adding an export turns this into a "module"
|
||||
export default {};
|
||||
|
|
|
@ -47,13 +47,18 @@ let choose1 = <Specific1>{ common: '5' };
|
|||
type CatStatus = 'alive' | 'dead' | 'both';
|
||||
|
||||
// Classes
|
||||
class Animal {}
|
||||
class Animal { }
|
||||
|
||||
// Illustration purposes only
|
||||
// In real apps, avoid inheritance if possible
|
||||
// noted exception: React.Component with react@<16.8.0
|
||||
class Cat extends Animal {}
|
||||
class Dog extends Animal {}
|
||||
class Cat extends Animal { }
|
||||
class Dog extends Animal { }
|
||||
|
||||
// adding an export to turn this into a "module"
|
||||
// Any Type - avoid if possible
|
||||
let mystery: any = "I don't like the person who will be maintaining this code";
|
||||
mystery = 2;
|
||||
mystery = () => 3;
|
||||
|
||||
// adding an export turns this into a "module"
|
||||
export default {};
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<body class="ms-Fabric">
|
||||
<div id="markdownReadme"></div>
|
||||
<div id="app">
|
||||
Nothing to show here, just look at your console window for output. Hit F12 to open console window.
|
||||
Nothing to show here; just look at your console window for output. Hit F12 (<code>cmd+option+I</code> on Mac) to open the console window.
|
||||
</div>
|
||||
<script src="../../assets/scripts.js"></script>
|
||||
</body>
|
||||
|
|
|
@ -19,12 +19,12 @@ function makePromise() {
|
|||
return Promise.resolve(5);
|
||||
}
|
||||
|
||||
// Do the exercises here, output your results with "console.log()" function
|
||||
// Do the exercises here, outputting results using console.log()
|
||||
// ...
|
||||
console.log('hello world');
|
||||
|
||||
async function run() {
|
||||
// Place your code for the async / await exercise here
|
||||
// Call the function you added for the async / await exercise here
|
||||
// ...
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
// TODO: create a Stack<T> generic class here:
|
||||
|
||||
/**
|
||||
*
|
||||
* export class Stack<T> {
|
||||
* push(...) { ... }
|
||||
* pop(...) { ... }
|
||||
* }
|
||||
*
|
||||
*/
|
||||
// export class Stack<T> {
|
||||
// push(...) { ... }
|
||||
// pop(...) { ... }
|
||||
// }
|
||||
|
|
|
@ -2,38 +2,38 @@
|
|||
|
||||
[Lessons](../) | [Exercise](./exercise/) | [Demo](./demo/)
|
||||
|
||||
UI Fabric is a Component Library that is developed to reflect the latest Microsoft design language. It is used in many Microsoft web applications and is developed in the open.
|
||||
[UI Fabric](https://developer.microsoft.com/en-us/fabric) is a component library that reflects the latest Microsoft design language. It is used in many Microsoft web applications and is [developed in the open](https://github.com/OfficeDev/office-ui-fabric-react).
|
||||
|
||||
We'll talk about:
|
||||
|
||||
- What makes it good
|
||||
- How to find it
|
||||
- How to use it
|
||||
- Laying out Apps with Stack
|
||||
- [What makes it good](#what-makes-it-good)
|
||||
- [How to find it](#how-to-find-it)
|
||||
- [How to use it](#how-to-use-it)
|
||||
- [Laying out apps with Stack](#layout-with-stack)
|
||||
|
||||
# What Makes It Good
|
||||
## What Makes It Good
|
||||
|
||||
- Fabric has been developed BOTH by developers and design engineers working together as a team
|
||||
- Most notable Microsoft web products use it
|
||||
- It is documented both from examples and generated from API (TypeScript) itself
|
||||
- It is documented both with examples and TypeScript API documentation
|
||||
- Components are highly customizable and themeable
|
||||
- Comprehensive library
|
||||
- Works with (aria) and augments (focus management) web accessibility standards
|
||||
- Works with assistive technologies and conforms to web accessibility standards for focus management
|
||||
- Fully funded and well managed - shield rotation and lots of automation work
|
||||
- Engineering is done in the open in github.com
|
||||
- Engineering is done in the open on GitHub
|
||||
- Engineering system is shared and re-usable by other teams
|
||||
|
||||
# How to Find It
|
||||
## How to Find It
|
||||
|
||||
github repo:
|
||||
GitHub repo:
|
||||
https://github.com/officedev/office-ui-fabric-react
|
||||
|
||||
Documentation can be found here:
|
||||
Documentation:
|
||||
https://developer.microsoft.com/en-us/fabric/#/components
|
||||
|
||||
# How to Use It
|
||||
## How to Use It
|
||||
|
||||
## Importing a Component
|
||||
### Importing a Component
|
||||
|
||||
```jsx
|
||||
import { DefaultButton } from 'office-ui-fabric-react';
|
||||
|
@ -47,11 +47,11 @@ const MyComponent = () => {
|
|||
};
|
||||
```
|
||||
|
||||
## Customizing Behavior of Individual Component
|
||||
### Customizing Behavior of Individual Components
|
||||
|
||||
Take a look at the documentation: https://developer.microsoft.com/en-us/fabric#/components/button
|
||||
Take a look at the [Button documentation](https://developer.microsoft.com/en-us/fabric#/components/button).
|
||||
|
||||
Let's say we want an Icon to be rendered with the Button Text, we'd use the `iconProps`
|
||||
From the documentation, we can see that if we want to render an icon along with the button's text, we can pass `iconProps` to the button:
|
||||
|
||||
```js
|
||||
import { DefaultButton } from 'office-ui-fabric-react';
|
||||
|
@ -65,9 +65,9 @@ const MyComponent = () => {
|
|||
};
|
||||
```
|
||||
|
||||
## Render Props
|
||||
### Customizing Component Rendering
|
||||
|
||||
Some Fabric components take in a render function like the TextField:
|
||||
Some Fabric components take in a render functions to allow customizing certain parts of the component. An example with TextField:
|
||||
|
||||
```js
|
||||
import { TextField } from 'office-ui-fabric-react';
|
||||
|
@ -82,36 +82,36 @@ const MyComponent = () => {
|
|||
};
|
||||
```
|
||||
|
||||
# Layout with Stack
|
||||
## Layout with Stack
|
||||
|
||||
Before we start, let's look at flexbox. It is really, really complex to use:
|
||||
Before we start, let's look at flexbox--a new CSS layout method which is powerful, but really, really complex to use:
|
||||
|
||||
- a guide: https://css-tricks.com/snippets/css/a-guide-to-flexbox/
|
||||
- a tool: http://the-echoplex.net/flexyboxes/
|
||||
- did you know there were 3 or so flex box standards? i.e. Old links will have non-working code
|
||||
- A guide: https://css-tricks.com/snippets/css/a-guide-to-flexbox/
|
||||
- A tool: http://the-echoplex.net/flexyboxes/
|
||||
- Did you know there were three or so flexbox standards? (this means old articles may have non-working code)
|
||||
|
||||
Fabric's answer is: Stack
|
||||
Fabric's answer is: Stack.
|
||||
|
||||
> A Stack is a container-type component that abstracts the implementation of a flexbox in order to define the layout of its children components.
|
||||
**Stack** is a container-type component that abstracts the usage of flexbox to define the layout of its child components.
|
||||
|
||||
The concepts are:
|
||||
Flexbox uses CSS styles to control:
|
||||
|
||||
- direction
|
||||
- grow
|
||||
- shrink
|
||||
- wrap
|
||||
- shrunk
|
||||
- justify-content
|
||||
- justification
|
||||
- alignment
|
||||
|
||||
Stack abstracts these CSS and provides a type discoverable.
|
||||
Stack abstracts these CSS styles and provides typings to make them more discoverable.
|
||||
|
||||
Checkout a cookbook of sorts in our documentation: https://developer.microsoft.com/en-us/fabric#/components/stack
|
||||
Check out a cookbook of sorts in our [documentation](https://developer.microsoft.com/en-us/fabric#/components/stack).
|
||||
|
||||
# Exercise
|
||||
|
||||
1. Open up the [Documentation for DefaultButton](https://developer.microsoft.com/en-us/fabric/#/components/button)
|
||||
2. Open up the TSX files inside `components/`
|
||||
3. Replace the DOM tags with Fabric components in those TSX files with these components:
|
||||
1. Open the [documentation for DefaultButton](https://developer.microsoft.com/en-us/fabric/#/components/button)
|
||||
2. Open each TSX file inside `exercise/src/components`
|
||||
3. Replace some of the built-in HTML tags with these Fabric components:
|
||||
|
||||
- Stack
|
||||
- DefaultButton
|
||||
|
@ -119,10 +119,12 @@ Checkout a cookbook of sorts in our documentation: https://developer.microsoft.c
|
|||
- TextField
|
||||
- Pivot (for the filter)
|
||||
|
||||
# Bonus Exercise
|
||||
> Hint: think about how you can use Stacks to make the layout look nicer.
|
||||
|
||||
GO WILD! There are so many components from the Fabric library! Try to put some components in the exercise component files. Try out these concepts that we have mentioned above:
|
||||
## Bonus Exercise
|
||||
|
||||
GO WILD! There are so many components in the Fabric library! Try to put some components in the exercise component files. Try out these concepts mentioned earlier:
|
||||
|
||||
- Importing components from `office-ui-fabric-react`
|
||||
- Customizing component with props found on the documentation site
|
||||
- Customize component with render props (these will be called onRender or something like that)
|
||||
- Customize component with render props (these will be called onRender___ or similar)
|
||||
|
|
|
@ -13,7 +13,7 @@ export const TodoFooter = (props: TodoFooterProps) => {
|
|||
return (
|
||||
<Stack horizontal horizontalAlign="space-between">
|
||||
<Text>
|
||||
{itemCount} item{itemCount > 1 ? 's' : ''} left
|
||||
{itemCount} item{itemCount === 1 ? '' : 's'} left
|
||||
</Text>
|
||||
<DefaultButton onClick={() => props.clear()}>Clear Completed</DefaultButton>
|
||||
</Stack>
|
||||
|
|
|
@ -6,7 +6,7 @@ export const TodoFooter = (props: any) => {
|
|||
return (
|
||||
<footer>
|
||||
<span>
|
||||
{itemCount} item{itemCount > 1 ? 's' : ''} left
|
||||
{itemCount} item{itemCount <= 1 ? '' : 's'} left
|
||||
</span>
|
||||
<button onClick={() => props.clear()} className="button">
|
||||
Clear Completed
|
||||
|
|
|
@ -13,7 +13,7 @@ export const TodoFooter = (props: TodoFooterProps) => {
|
|||
return (
|
||||
<Stack horizontal horizontalAlign="space-between">
|
||||
<Text>
|
||||
{itemCount} item{itemCount > 1 ? 's' : ''} left
|
||||
{itemCount} item{itemCount === 1 ? '' : 's'} left
|
||||
</Text>
|
||||
<DefaultButton onClick={() => props.clear()}>Clear Completed</DefaultButton>
|
||||
</Stack>
|
||||
|
|
|
@ -34,7 +34,7 @@ export class TodoHeader extends React.Component<TodoHeaderProps, TodoHeaderState
|
|||
styles={props => ({
|
||||
...(props.focused && {
|
||||
field: {
|
||||
backgroundColor: 'black'
|
||||
backgroundColor: '#c7e0f4'
|
||||
}
|
||||
})
|
||||
})}
|
||||
|
|
|
@ -16,7 +16,7 @@ export const TodoFooter = (props: TodoFooterProps) => {
|
|||
return (
|
||||
<Stack horizontal horizontalAlign="space-between">
|
||||
<Text>
|
||||
{itemCount} item{itemCount > 1 ? 's' : ''} left
|
||||
{itemCount} item{itemCount === 1 ? '' : 's'} left
|
||||
</Text>
|
||||
<DefaultButton onClick={() => props.clear()}>Clear Completed</DefaultButton>
|
||||
</Stack>
|
||||
|
|
|
@ -58,7 +58,7 @@ We begin the journey into Redux by looking at the store. The store consists of s
|
|||
|
||||
## Create Store
|
||||
|
||||
The `createStore()` takes in several arguments. The simplest for just takes in reducers. Reducers are the means by which the state changes from one snapshot to another.
|
||||
The `createStore()` takes in several arguments. The simplest form just takes in reducers. Reducers are the means by which the state changes from one snapshot to another.
|
||||
|
||||
```ts
|
||||
const store = createStore(reducer);
|
||||
|
|
|
@ -15,7 +15,7 @@ const TodoFooter = (props: TodoFooterProps) => {
|
|||
return (
|
||||
<Stack horizontal horizontalAlign="space-between">
|
||||
<Text>
|
||||
{itemCount} item{itemCount > 1 ? 's' : ''} left
|
||||
{itemCount} item{itemCount === 1 ? '' : 's'} left
|
||||
</Text>
|
||||
<DefaultButton onClick={() => props.clear()}>Clear Completed</DefaultButton>
|
||||
</Stack>
|
||||
|
|
|
@ -16,7 +16,7 @@ export const TodoFooter = (props: TodoFooterProps) => {
|
|||
return (
|
||||
<Stack horizontal horizontalAlign="space-between">
|
||||
<Text>
|
||||
{itemCount} item{itemCount > 1 ? 's' : ''} left
|
||||
{itemCount} item{itemCount === 1 ? '' : 's'} left
|
||||
</Text>
|
||||
<DefaultButton onClick={() => props.clear()}>Clear Completed</DefaultButton>
|
||||
</Stack>
|
||||
|
|
|
@ -15,7 +15,7 @@ const TodoFooter = (props: TodoFooterProps) => {
|
|||
return (
|
||||
<Stack horizontal horizontalAlign="space-between">
|
||||
<Text>
|
||||
{itemCount} item{itemCount > 1 ? 's' : ''} left
|
||||
{itemCount} item{itemCount === 1 ? '' : 's'} left
|
||||
</Text>
|
||||
<DefaultButton onClick={() => props.clear()}>Clear Completed</DefaultButton>
|
||||
</Stack>
|
||||
|
|
|
@ -29,7 +29,7 @@ class TodoHeader extends React.Component<TodoHeaderProps, TodoHeaderState> {
|
|||
|
||||
<Stack horizontal gap={10}>
|
||||
<Stack.Item grow>
|
||||
<TextField placeholder="What needs toasdf be done?" value={this.state.labelInput} onChange={this.onChange} />
|
||||
<TextField placeholder="What needs to be done?" value={this.state.labelInput} onChange={this.onChange} />
|
||||
</Stack.Item>
|
||||
<PrimaryButton onClick={this.onAdd}>Add</PrimaryButton>
|
||||
</Stack>
|
||||
|
|
|
@ -15,7 +15,7 @@ const TodoFooter = (props: TodoFooterProps) => {
|
|||
return (
|
||||
<Stack horizontal horizontalAlign="space-between">
|
||||
<Text>
|
||||
{itemCount} item{itemCount > 1 ? 's' : ''} left
|
||||
{itemCount} item{itemCount === 1 ? '' : 's'} left
|
||||
</Text>
|
||||
<DefaultButton onClick={() => props.clear()}>Clear Completed</DefaultButton>
|
||||
</Stack>
|
||||
|
|
|
@ -15,7 +15,7 @@ const TodoFooter = (props: TodoFooterProps) => {
|
|||
return (
|
||||
<Stack horizontal horizontalAlign="space-between">
|
||||
<Text>
|
||||
{itemCount} item{itemCount > 1 ? 's' : ''} left
|
||||
{itemCount} item{itemCount === 1 ? '' : 's'} left
|
||||
</Text>
|
||||
<DefaultButton onClick={() => props.clear()}>Clear Completed</DefaultButton>
|
||||
</Stack>
|
||||
|
|
|
@ -15,7 +15,7 @@ const TodoFooter = (props: TodoFooterProps) => {
|
|||
return (
|
||||
<Stack horizontal horizontalAlign="space-between">
|
||||
<Text>
|
||||
{itemCount} item{itemCount > 1 ? 's' : ''} left
|
||||
{itemCount} item{itemCount === 1 ? '' : 's'} left
|
||||
</Text>
|
||||
<DefaultButton onClick={() => props.clear()}>Clear Completed</DefaultButton>
|
||||
</Stack>
|
||||
|
|
Загрузка…
Ссылка в новой задаче