Merge branch 'master' into start
This commit is contained in:
Коммит
2b17b1495d
|
@ -9,7 +9,7 @@ hljs.registerLanguage('typescript', typescript);
|
|||
async function run() {
|
||||
const div = document.getElementById('markdownReadme');
|
||||
|
||||
// // Create your custom renderer.
|
||||
// Create your custom renderer.
|
||||
const renderer = new Renderer();
|
||||
renderer.code = (code, language) => {
|
||||
// Check whether the given language is valid for highlight.js.
|
||||
|
@ -21,24 +21,11 @@ async function run() {
|
|||
};
|
||||
marked.setOptions({ renderer });
|
||||
|
||||
// if (typeof hljs != 'undefined') {
|
||||
// marked.setOptions({
|
||||
// highlight: function(code, lang) {
|
||||
// if (lang && hljs.getLanguage(lang)) {
|
||||
// return hljs.highlight(lang, code).value;
|
||||
// } else {
|
||||
// return code;
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
if (div) {
|
||||
const response = await fetch('../README.md');
|
||||
const markdownText = await response.text();
|
||||
div.innerHTML = marked(markdownText, { baseUrl: '../' });
|
||||
restoreScroll(div);
|
||||
}
|
||||
|
||||
div.addEventListener('scroll', evt => {
|
||||
saveScroll(div);
|
||||
|
@ -48,6 +35,7 @@ async function run() {
|
|||
saveScroll(div);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const scrollKey = `${window.location.pathname}_scrolltop`;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
export class App extends React.Component {
|
||||
export class App extends React.Component<any, any> {
|
||||
render() {
|
||||
let text = 'My App';
|
||||
return (
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import { Counter } from './components/Counter';
|
||||
|
||||
export class App extends React.Component {
|
||||
export class App extends React.Component<any, any> {
|
||||
render() {
|
||||
return (
|
||||
<div className="App">
|
||||
|
|
|
@ -3,7 +3,7 @@ import { TodoFooter } from './components/TodoFooter';
|
|||
import { TodoHeader } from './components/TodoHeader';
|
||||
import { TodoList } from './components/TodoList';
|
||||
|
||||
export class TodoApp extends React.Component {
|
||||
export class TodoApp extends React.Component<any, any> {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
export class TodoHeader extends React.Component {
|
||||
export class TodoHeader extends React.Component<any, any> {
|
||||
render() {
|
||||
return (
|
||||
<header>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
export class TodoListItem extends React.Component {
|
||||
export class TodoListItem extends React.Component<any, any> {
|
||||
render() {
|
||||
return <div />;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import { TodoFooter } from './components/TodoFooter';
|
|||
import { TodoHeader } from './components/TodoHeader';
|
||||
import { TodoList } from './components/TodoList';
|
||||
|
||||
export class TodoApp extends React.Component {
|
||||
export class TodoApp extends React.Component<any, any> {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
export class TodoHeader extends React.Component {
|
||||
export class TodoHeader extends React.Component<any, any> {
|
||||
render() {
|
||||
return (
|
||||
<header>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from "react";
|
||||
|
||||
export class TodoListItem extends React.Component {
|
||||
export class TodoListItem extends React.Component<any, any> {
|
||||
render() {
|
||||
return (
|
||||
<li className="todo">
|
||||
|
|
|
@ -3,7 +3,7 @@ import { TodoFooter } from './components/TodoFooter';
|
|||
import { TodoHeader } from './components/TodoHeader';
|
||||
import { TodoList } from './components/TodoList';
|
||||
|
||||
export class TodoApp extends React.Component {
|
||||
export class TodoApp extends React.Component<any, any> {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
export class TodoHeader extends React.Component {
|
||||
export class TodoHeader extends React.Component<any, any> {
|
||||
render() {
|
||||
return (
|
||||
<header>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from "react";
|
||||
|
||||
export class TodoListItem extends React.Component {
|
||||
export class TodoListItem extends React.Component<any, any> {
|
||||
render() {
|
||||
return (
|
||||
<li className="todo">
|
||||
|
|
|
@ -3,7 +3,7 @@ import { TodoFooter } from './components/TodoFooter';
|
|||
import { TodoHeader } from './components/TodoHeader';
|
||||
import { TodoList } from './components/TodoList';
|
||||
|
||||
export class TodoApp extends React.Component {
|
||||
export class TodoApp extends React.Component<any, any> {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from "react";
|
||||
|
||||
export class TodoListItem extends React.Component {
|
||||
export class TodoListItem extends React.Component<any, any> {
|
||||
render() {
|
||||
return (
|
||||
<li className="todo">
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Types and Creating a UI Driven State
|
||||
# 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`. Values from the state are then passed down to the UI as props.
|
||||
|
||||
|
|
|
@ -169,35 +169,35 @@ Exercises will be completed under this step's `exercise/src` folder unless other
|
|||
|
||||
## Modules
|
||||
|
||||
1. Open the file `exercise/src/index.ts` in VS Code
|
||||
1. Open the file `exercise/src/fibonacci.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 the `n`-th Fibonacci number (be sure the specify the type of `n`).
|
||||
2. Inside this file, 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); }`
|
||||
|
||||
4. Export `fib(n)` as a **named export**
|
||||
3. Export `fib(n)` as a **named export**
|
||||
|
||||
5. Export another const variable as a **default export**
|
||||
4. Export a const variable `FibConst` as a **default export**
|
||||
|
||||
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)`.
|
||||
5. Inside `index.ts` in the same folder, import both `fib` and `FibConst`, and use the built-in `console.log()` function to log the result of `fib(FibConst)`.
|
||||
|
||||
## Types and Interfaces
|
||||
|
||||
Inside `index.ts`:
|
||||
Inside `exercise/src/index.ts`:
|
||||
|
||||
1. Add a type alias for string union type describing the states of Red-Green-Yellow traffic light: `type TrafficLight = ???`
|
||||
|
||||
2. Describe a type of car with an interface: `interface Car { ... }` complete with `wheels`, `color`, `make`, `model`
|
||||
|
||||
3. Create a valid car instance and log it using `console.log()`: `const myCar: Car = { ??? }`;
|
||||
|
||||
## Generics
|
||||
|
||||
Inside `stack.ts`, create a generic class for a `Stack<T>` complete with a typed `pop()` and `push()` methods.
|
||||
Inside `exercise/src/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>`.
|
||||
In `exercise/src/index.ts`, create a `Stack<number>` and use `console.log()` to demonstrate its functionality.
|
||||
|
||||
## Spread and Destructuring
|
||||
|
||||
|
@ -218,9 +218,9 @@ const obj2 = {
|
|||
};
|
||||
```
|
||||
|
||||
2. Now create a one-liner using the spread syntax `{...x, ...y}` to create a new variable that combines these two objects.
|
||||
2. Now create a one-liner using the spread syntax `{...x, ...y}` to create a new variable `megaObj` that combines these two objects.
|
||||
|
||||
3. Use the destructuring syntax to retrieve the values for `{first, second, catcher}` from the new object created in step (2).
|
||||
3. Use the destructuring syntax to retrieve the values for `{first, second, catcher}` from `megaObj`.
|
||||
|
||||
## Async / Await
|
||||
|
||||
|
@ -232,6 +232,6 @@ function makePromise() {
|
|||
}
|
||||
```
|
||||
|
||||
1. 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.
|
||||
|
||||
2. Create a new function that uses the `async` keyword. Make an `await` call to `makePromise()` and return the results.
|
||||
2. Create a new function that uses the `async` keyword. Inside the function, make an `await` call to `makePromise()` and return the results.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// TODO: create a named export of a function called fib(n)
|
||||
// export function fib(n: number) ...
|
||||
|
||||
// TODO: create a default export of a constant of a number
|
||||
// TODO: create a default export of a constant of a number FibConst
|
||||
// export default ...
|
||||
|
|
|
@ -1,7 +1,32 @@
|
|||
// TODO: import the fib(n) function and the constant from './fibonacci.ts'
|
||||
// import {fib}, FibConst from ...
|
||||
|
||||
// Some setup code for exercises
|
||||
// TODO: import Stack from './stack.ts'
|
||||
|
||||
// Do the exercises here, outputting results using console.log()
|
||||
console.log('hello world');
|
||||
|
||||
// ---- Modules ----
|
||||
|
||||
// TODO: log the result of fib(FibConst)
|
||||
|
||||
// ---- Types and Interfaces ----
|
||||
|
||||
// TODO: define TrafficLight type
|
||||
// type TrafficLight = ???
|
||||
|
||||
// TODO: define Car interface
|
||||
// interface Car { ??? }
|
||||
|
||||
// TODO: create Car instance
|
||||
// const myCar: Car = { ??? }
|
||||
|
||||
// ---- Generics ----
|
||||
|
||||
// TODO: Demonstrate the Stack
|
||||
// const myStack: Stack<number> = ???
|
||||
|
||||
// ---- Spread and Destructuring ----
|
||||
const obj1 = {
|
||||
first: 'who',
|
||||
second: 'what',
|
||||
|
@ -15,17 +40,16 @@ const obj2 = {
|
|||
catcher: 'today'
|
||||
};
|
||||
|
||||
// TODO: combine obj1 and obj2 into a single object megaObj using spread syntax
|
||||
// const megaObj = ???
|
||||
|
||||
// TODO: use destructuring syntax to extract { first, second, catcher }
|
||||
|
||||
// ---- Async / Await ----
|
||||
function makePromise() {
|
||||
return Promise.resolve(5);
|
||||
}
|
||||
|
||||
// Do the exercises here, outputting results using console.log()
|
||||
// ...
|
||||
console.log('hello world');
|
||||
// TODO: call makePromise() using await syntax and log the results
|
||||
|
||||
async function run() {
|
||||
// Call the function you added for the async / await exercise here
|
||||
// ...
|
||||
}
|
||||
|
||||
run();
|
||||
// TODO: create a new async function
|
||||
|
|
|
@ -107,21 +107,21 @@ Stack abstracts these CSS styles and provides typings to make them more discover
|
|||
|
||||
Check out a cookbook of sorts in our [documentation](https://developer.microsoft.com/en-us/fabric#/components/stack).
|
||||
|
||||
# Exercise
|
||||
# Exercise 1: Getting familiar with the Fabric documentation site:
|
||||
|
||||
Open the [documentation for DefaultButton](https://developer.microsoft.com/en-us/fabric/#/components/button). Use the sidebar to explore other available components.
|
||||
|
||||
# Exercise 2: "Fabric"ize the TodoFooter.tsx
|
||||
|
||||
If you don't already have the app running, start it by running `npm start` from the root of the `frontend-bootcamp` folder. Click the "exercise" link under day 2 step 2 to see results.
|
||||
|
||||
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:
|
||||
1. Open TSX file inside `exercise/src/components/TodoFooter.tsx`
|
||||
2. Follow the top TODO comment to import Stack, Text and DefaultButton components from Fabric
|
||||
3. Follow the TODO comment to:
|
||||
|
||||
- Stack
|
||||
- DefaultButton
|
||||
- Checkbox
|
||||
- TextField
|
||||
- Pivot (for the filter)
|
||||
|
||||
> Hint: think about how you can use Stacks to make the layout look nicer.
|
||||
- replace `<footer>` with a `<Stack>`
|
||||
- replace `<span>` with a `<Text>`
|
||||
- replace `<button>` with a `<DefaultButton>`
|
||||
|
||||
## Bonus Exercise
|
||||
|
||||
|
@ -129,4 +129,4 @@ GO WILD! There are so many components in the Fabric library! Try to put some com
|
|||
|
||||
- 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 similar)
|
||||
- Customize component with render props (these will be called onRender\_\_\_ or similar)
|
||||
|
|
|
@ -11,7 +11,10 @@ export class TodoApp extends React.Component<any, Store> {
|
|||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
todos: {},
|
||||
todos: {
|
||||
id0: { label: 'hello', completed: false },
|
||||
id1: { label: 'world', completed: false }
|
||||
},
|
||||
filter: 'all'
|
||||
};
|
||||
}
|
||||
|
|
|
@ -5,16 +5,86 @@ import { TodoHeader } from './TodoHeader';
|
|||
import { TodoList } from './TodoList';
|
||||
import { Store } from '../store';
|
||||
|
||||
let index = 0;
|
||||
|
||||
export class TodoApp extends React.Component<any, Store> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
todos: {
|
||||
id0: { label: 'hello', completed: false },
|
||||
id1: { label: 'world', completed: false }
|
||||
},
|
||||
filter: 'all'
|
||||
};
|
||||
}
|
||||
render() {
|
||||
const { filter, todos } = this.state;
|
||||
return (
|
||||
<Stack horizontalAlign="center">
|
||||
<Stack style={{ width: 400 }} gap={25}>
|
||||
<TodoHeader />
|
||||
<TodoList />
|
||||
<TodoFooter />
|
||||
<TodoHeader addTodo={this._addTodo} setFilter={this._setFilter} filter={filter} />
|
||||
<TodoList complete={this._complete} todos={todos} filter={filter} remove={this._remove} edit={this._edit} />
|
||||
<TodoFooter clear={this._clear} todos={todos} />
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
private _addTodo = label => {
|
||||
const { todos } = this.state;
|
||||
const id = index++;
|
||||
|
||||
this.setState({
|
||||
todos: { ...todos, [id]: { label } }
|
||||
});
|
||||
};
|
||||
|
||||
private _remove = id => {
|
||||
const newTodos = { ...this.state.todos };
|
||||
delete newTodos[id];
|
||||
|
||||
this.setState({
|
||||
todos: newTodos
|
||||
});
|
||||
};
|
||||
|
||||
private _complete = id => {
|
||||
const newTodos = { ...this.state.todos };
|
||||
newTodos[id].completed = !newTodos[id].completed;
|
||||
|
||||
this.setState({
|
||||
todos: newTodos
|
||||
});
|
||||
};
|
||||
|
||||
private _edit = (id, label) => {
|
||||
const newTodos = { ...this.state.todos };
|
||||
newTodos[id] = { ...newTodos[id], label };
|
||||
|
||||
this.setState({
|
||||
todos: newTodos
|
||||
});
|
||||
};
|
||||
|
||||
private _clear = () => {
|
||||
const { todos } = this.state;
|
||||
const newTodos = {};
|
||||
|
||||
Object.keys(this.state.todos).forEach(id => {
|
||||
if (!todos[id].completed) {
|
||||
newTodos[id] = todos[id];
|
||||
}
|
||||
});
|
||||
|
||||
this.setState({
|
||||
todos: newTodos
|
||||
});
|
||||
};
|
||||
|
||||
private _setFilter = filter => {
|
||||
this.setState({
|
||||
filter: filter
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,7 +1,20 @@
|
|||
import React from 'react';
|
||||
import { Store } from '../store';
|
||||
|
||||
export const TodoFooter = (props: any) => {
|
||||
const itemCount = 3;
|
||||
// TODO: import DefaultButton, Stack, and Text
|
||||
|
||||
interface TodoFooterProps {
|
||||
clear: () => void;
|
||||
todos: Store['todos'];
|
||||
}
|
||||
|
||||
export const TodoFooter = (props: TodoFooterProps) => {
|
||||
const itemCount = Object.keys(props.todos).filter(id => !props.todos[id].completed).length;
|
||||
|
||||
// TODO:
|
||||
// 1. replace the <footer> with the Fabric control <Stack>
|
||||
// 2. replace the <span> with the Fabric control <Text>
|
||||
// 3. replace the <button> with Fabric control <DefaultButton>
|
||||
|
||||
return (
|
||||
<footer>
|
||||
|
|
|
@ -1,19 +1,56 @@
|
|||
import React from 'react';
|
||||
import { Stack, Text, Pivot, PivotItem, TextField, PrimaryButton } from 'office-ui-fabric-react';
|
||||
import { FilterTypes } from '../store';
|
||||
|
||||
export class TodoHeader extends React.Component<{}, {}> {
|
||||
interface TodoHeaderProps {
|
||||
addTodo: (label: string) => void;
|
||||
setFilter: (filter: FilterTypes) => void;
|
||||
filter: string;
|
||||
}
|
||||
|
||||
interface TodoHeaderState {
|
||||
labelInput: string;
|
||||
}
|
||||
|
||||
export class TodoHeader extends React.Component<TodoHeaderProps, TodoHeaderState> {
|
||||
constructor(props: TodoHeaderProps) {
|
||||
super(props);
|
||||
this.state = { labelInput: undefined };
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h1>todos - step2-02 exercise</h1>
|
||||
<input className="textfield" placeholder="add todo" />
|
||||
<button className="button add">Add</button>
|
||||
<div className="filter">
|
||||
<button>all</button>
|
||||
<button>active</button>
|
||||
<button>completed</button>
|
||||
</div>
|
||||
</div>
|
||||
<Stack>
|
||||
<Stack horizontal horizontalAlign="center">
|
||||
<Text variant="xxLarge">todos - step2-02 demo</Text>
|
||||
</Stack>
|
||||
|
||||
<Stack horizontal>
|
||||
<Stack.Item grow>
|
||||
<TextField placeholder="What needs to be done?" value={this.state.labelInput} onChange={this.onChange} />
|
||||
</Stack.Item>
|
||||
<PrimaryButton onClick={this.onAdd}>Add</PrimaryButton>
|
||||
</Stack>
|
||||
|
||||
<Pivot onLinkClick={this.onFilter}>
|
||||
<PivotItem headerText="all" />
|
||||
<PivotItem headerText="active" />
|
||||
<PivotItem headerText="completed" />
|
||||
</Pivot>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
private onAdd = () => {
|
||||
this.props.addTodo(this.state.labelInput);
|
||||
this.setState({ labelInput: undefined });
|
||||
};
|
||||
|
||||
private onChange = (evt: React.FormEvent<HTMLInputElement>, newValue: string) => {
|
||||
this.setState({ labelInput: newValue });
|
||||
};
|
||||
|
||||
private onFilter = (item: PivotItem) => {
|
||||
this.props.setFilter(item.props.headerText as FilterTypes);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,13 +1,27 @@
|
|||
import React from 'react';
|
||||
import { Stack } from 'office-ui-fabric-react';
|
||||
import { TodoListItem } from './TodoListItem';
|
||||
import { Store, FilterTypes } from '../store';
|
||||
|
||||
interface TodoListProps {
|
||||
complete: (id: string) => void;
|
||||
remove: (id: string) => void;
|
||||
todos: Store['todos'];
|
||||
filter: FilterTypes;
|
||||
edit: (id: string, label: string) => void;
|
||||
}
|
||||
|
||||
export const TodoList = (props: TodoListProps) => {
|
||||
const { filter, todos, complete, remove, edit } = props;
|
||||
const filteredTodos = Object.keys(todos).filter(id => {
|
||||
return filter === 'all' || (filter === 'completed' && todos[id].completed) || (filter === 'active' && !todos[id].completed);
|
||||
});
|
||||
|
||||
export const TodoList = (props: any) => {
|
||||
return (
|
||||
<ul className="todos">
|
||||
<TodoListItem />
|
||||
<TodoListItem />
|
||||
<TodoListItem />
|
||||
</ul>
|
||||
<Stack gap={10}>
|
||||
{filteredTodos.map(id => (
|
||||
<TodoListItem key={id} id={id} todos={todos} complete={complete} remove={remove} edit={edit} />
|
||||
))}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,13 +1,75 @@
|
|||
import React from 'react';
|
||||
import { Stack, Checkbox, IconButton, TextField, DefaultButton } from 'office-ui-fabric-react';
|
||||
import { Store } from '../store';
|
||||
|
||||
interface TodoListItemProps {
|
||||
id: string;
|
||||
todos: Store['todos'];
|
||||
remove: (id: string) => void;
|
||||
complete: (id: string) => void;
|
||||
edit: (id: string, label: string) => void;
|
||||
}
|
||||
|
||||
interface TodoListItemState {
|
||||
editing: boolean;
|
||||
editLabel: string;
|
||||
}
|
||||
|
||||
export class TodoListItem extends React.Component<TodoListItemProps, TodoListItemState> {
|
||||
constructor(props: TodoListItemProps) {
|
||||
super(props);
|
||||
this.state = { editing: false, editLabel: undefined };
|
||||
}
|
||||
|
||||
export class TodoListItem extends React.Component<any, any> {
|
||||
render() {
|
||||
const { todos, id, complete, remove } = this.props;
|
||||
const item = todos[id];
|
||||
|
||||
return (
|
||||
<li className="todo">
|
||||
<label>
|
||||
<input type="checkbox" defaultChecked={false} /> test item
|
||||
</label>
|
||||
</li>
|
||||
<Stack horizontal verticalAlign="center" horizontalAlign="space-between">
|
||||
{!this.state.editing && (
|
||||
<>
|
||||
<Checkbox label={item.label} checked={item.completed} onChange={() => complete(id)} />
|
||||
<div>
|
||||
<IconButton iconProps={{ iconName: 'Edit' }} className="clearButton" onClick={this.onEdit} />
|
||||
<IconButton iconProps={{ iconName: 'Cancel' }} className="clearButton" onClick={() => remove(id)} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{this.state.editing && (
|
||||
<Stack.Item grow>
|
||||
<Stack horizontal>
|
||||
<Stack.Item grow>
|
||||
<TextField value={this.state.editLabel} onChange={this.onChange} />
|
||||
</Stack.Item>
|
||||
<DefaultButton onClick={this.onDoneEdit}>Save</DefaultButton>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
private onEdit = () => {
|
||||
const { todos, id } = this.props;
|
||||
const { label } = todos[id];
|
||||
|
||||
this.setState({
|
||||
editing: true,
|
||||
editLabel: this.state.editLabel || label
|
||||
});
|
||||
};
|
||||
|
||||
private onDoneEdit = () => {
|
||||
this.props.edit(this.props.id, this.state.editLabel);
|
||||
this.setState({
|
||||
editing: false,
|
||||
editLabel: undefined
|
||||
});
|
||||
};
|
||||
|
||||
private onChange = (evt: React.FormEvent<HTMLInputElement>, newValue: string) => {
|
||||
this.setState({ editLabel: newValue });
|
||||
};
|
||||
}
|
||||
|
|
|
@ -87,28 +87,27 @@ The `styles` prop can take either an object, or a function which returns a style
|
|||
|
||||
The following code (simplified from `demo/src/components/TodoHeader.tsx`) demonstrates using `styles` to customize individual components. The TextField uses a style function and the PrimaryButton uses a style object.
|
||||
|
||||
```tsx
|
||||
```ts
|
||||
function render() {
|
||||
return (
|
||||
<Stack>
|
||||
<Stack.Item>
|
||||
<TextField
|
||||
placeholder="What needs to be done?"
|
||||
styles={(props: ITextFieldStyleProps): Partial<ITextFieldStyles> => ({
|
||||
const buttonStyles = {
|
||||
root: { backgroundColor: 'maroon' },
|
||||
rootHovered: { background: 'green' }
|
||||
};
|
||||
|
||||
const textFieldStyles = (props: ITextFieldStyleProps): Partial<ITextFieldStyles> => ({
|
||||
...(props.focused && {
|
||||
field: {
|
||||
backgroundColor: '#c7e0f4'
|
||||
}
|
||||
})
|
||||
})}
|
||||
/>
|
||||
});
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Stack.Item>
|
||||
<TextField placeholder="What needs to be done?" styles={textFieldStyles} />
|
||||
</Stack.Item>
|
||||
<PrimaryButton styles={{
|
||||
root: { backgroundColor: 'maroon' },
|
||||
rootHovered: { background: 'green' }
|
||||
}}>
|
||||
Add
|
||||
</PrimaryButton>
|
||||
<PrimaryButton styles={buttonStyles}>Add</PrimaryButton>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
@ -121,6 +120,7 @@ function render() {
|
|||
This is an advanced approach which also works outside of Fabric. Within Fabric-based apps, you would typically only use `mergeStyles` in certain niche scenarios. (Fabric itself uses `mergeStyles` under the hood to power some of its styling.)
|
||||
|
||||
Benefits of `mergeStyles` include:
|
||||
|
||||
- Works in any app
|
||||
- Eliminates the need to import or bundle CSS stylesheets (all styles are bundled as normal JS code)
|
||||
- Provides type checking for styles (like Sass) when used with TypeScript
|
||||
|
@ -144,11 +144,7 @@ const className = mergeStyles(blueBackgroundClassName, {
|
|||
}
|
||||
});
|
||||
|
||||
const myDiv = (
|
||||
<div className={className}>
|
||||
I am a green div that turns red on hover!
|
||||
</div>
|
||||
);
|
||||
const myDiv = <div className={className}>I am a green div that turns red on hover!</div>;
|
||||
```
|
||||
|
||||
# Exercises
|
||||
|
|
|
@ -129,18 +129,16 @@ Note how tests are re-run when either test files or source files under `src` are
|
|||
|
||||
# Exercise
|
||||
|
||||
Start the test runner by running `npm test` in the root of the `frontend-bootcamp` folder.
|
||||
|
||||
## Basic testing
|
||||
|
||||
1. Run the tests by running `npm test` at the root of the bootcamp project
|
||||
1. Look at `exercise/src/stack.ts` for a sample implementation of a stack
|
||||
|
||||
2. Look at `exercise/src/stack.ts` for a sample implementation of a stack
|
||||
|
||||
3. Follow the instructions inside `stack.spec.ts` file to complete the two tests
|
||||
2. Follow the instructions inside `stack.spec.ts` file to complete the two tests
|
||||
|
||||
## Enzyme Testing
|
||||
|
||||
1. Open up `exercise/src/TestMe.spec.tsx`
|
||||
|
||||
2. Fill in the blank for the missing test using `enzyme` concepts introduced from the demo
|
||||
|
||||
3. Run tests with `npm test`
|
||||
2. Fill in the test using Enzyme concepts introduced in the demo
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
describe('stack', () => {
|
||||
// TODO: Import the stack here
|
||||
|
||||
describe('Stack', () => {
|
||||
it('should push item to the top of the stack', () => {
|
||||
// implement test here:
|
||||
// 1. require the stack
|
||||
// 2. create stack push calls to place some items in the stack
|
||||
// 3. write assertions via the expect() API
|
||||
// TODO: implement test here:
|
||||
// 1. Instantiate a new Stack - i.e. const stack = new Stack<number>();
|
||||
// 2. Use stack push calls to add some items to the stack
|
||||
// 3. Write assertions via the expect() API
|
||||
});
|
||||
|
||||
it('should pop the item from the top of stack', () => {
|
||||
// implement test here:
|
||||
// 1. require the stack
|
||||
// 2. create stack push calls to place some items in the stack
|
||||
// TODO: implement test here:
|
||||
// 1. Instantiate a new Stack - i.e. const stack = new Stack<number>();
|
||||
// 2. Use stack push calls to add some items to the stack
|
||||
// 3. pop a few items off the stack
|
||||
// 4. write assertions via the expect() API
|
||||
});
|
||||
|
|
|
@ -1,13 +1,27 @@
|
|||
export class Stack<T> {
|
||||
private _items: T[] = [];
|
||||
|
||||
/** Add an item to the top of the stack. */
|
||||
push(item: T) {
|
||||
this._items.push(item);
|
||||
}
|
||||
|
||||
/** Remove the top item from the stack and return it. */
|
||||
pop(): T {
|
||||
if (this._items.length > 0) {
|
||||
return this._items.pop();
|
||||
}
|
||||
}
|
||||
|
||||
/** Return the top item from the stack without removing it. */
|
||||
peek(): T {
|
||||
if (this._items.length > 0) {
|
||||
return this._items[this._items.length - 1];
|
||||
}
|
||||
}
|
||||
|
||||
/** Get the number of items in the stack/ */
|
||||
get count(): number {
|
||||
return this._items.length;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
[Lessons](../) | [Exercise](./exercise/) | [Demo](./demo/)
|
||||
|
||||
# Discussion on Flux
|
||||
## Flux
|
||||
|
||||
Ideally React gives us a mental model of:
|
||||
|
||||
|
@ -12,62 +12,66 @@ f(data) => view
|
|||
|
||||
This is fine if the data never changes. However, React exists to deal with data updates via props (immutable) and state (changes based on `setState()` API). In the real world, data is shaped like a tree and view is shaped like a tree. They don't always match.
|
||||
|
||||
Facebook invented a the Flux pattern to solve this shared state issue. Redux is an implementation of the Flux architectural pattern. Redux expects the data to be a singleton state tree that listens for messages to manipulate the state as appropriate:
|
||||
Facebook invented the [Flux](https://facebook.github.io/flux/) architectural pattern to solve this shared state issue. [Redux](https://redux.js.org/) is an implementation of Flux.
|
||||
|
||||
Redux is used inside many large, complex applications because of its clarity and predictability. It is really easy to debug and is easily extensible via its middleware architecture. In this lesson, we'll explore the heart of how Redux modifies state.
|
||||
|
||||
Redux expects the data (store) to be a singleton state tree. It listens for messages to manipulate the state and passes updates down to views.
|
||||
|
||||
![Flux Diagram](../assets/flux.png)
|
||||
|
||||
## View
|
||||
### View
|
||||
|
||||
These are the React Components that consume the store as its data. There is a special way Redux will map its data from the state tree into the different React Components. The Components will know to re-render when these bits of state are changed.
|
||||
A view is a React components that consumes the store as its data. There is a special way Redux will map its data from the state tree into the different React components. The components will know to re-render when these bits of state are changed.
|
||||
|
||||
## Action
|
||||
### Action
|
||||
|
||||
Actions are messages that represent some event, such as a user's action or a network request. With the aid of _reducers_, they affect the overall state.
|
||||
Actions are messages that represent some event, such as a user's action or a network request. With the aid of *reducers*, they affect the overall state.
|
||||
|
||||
## Store
|
||||
### Store
|
||||
|
||||
This is a singleton state tree. The state tree is immutable and needs to be re-created at every action. This helps connected views to know when to update itself - just doing a simple reference comparison rather than a deep comparison.
|
||||
The store contains a singleton state tree. The state tree is immutable and needs to be re-created at every action. This helps connected views easily to know when to update by allowing them to do a simple reference comparison rather than a deep comparison. You can think of each state as a snapshot of the app at that point in time.
|
||||
|
||||
## Reducers
|
||||
### Dispatcher
|
||||
|
||||
These are simple stateless, pure functions that takes state + action message and returns a copy of state some modifications according to the action message type and payload.
|
||||
There is a single dispatcher. It simply informs the store of all the actions that need to be performed. Additional middleware (explained later) can be applied to the store, and the dispatcher's job is to dispatch the message through all the middleware layers.
|
||||
|
||||
## Dispatcher
|
||||
### Reducers
|
||||
|
||||
There is a single dispatcher. It simply informs of the store of all the actions that needs to be performed. Certain middleware can be applied to the store and the dispatcher's job is to dispatch the message through all the middleware layers.
|
||||
Redux uses **reducers** to manage state changes. This name comes from the "reducer" function passed to `Array.reduce()`.
|
||||
|
||||
Redux is used inside many large and complex applications because of its clarity and its predictability. It is really easy to debug and is easily extensible via its middleware architecture. In this exercise, we'll explore the heart of how Redux modifies state.
|
||||
A Redux reducer is a simple stateless **pure function** (no side effects). Its only job is to change the state from one immutable snapshot to another. It takes state + an action message as input, makes a modified copy of the state based on the action message type and payload, and returns the new state. (Reducers should not modify the previous state.)
|
||||
|
||||
Redux uses what is called a "reducer" to modify its state. It is called this because a "reducer" is what is used inside an `Array.reduce()`.
|
||||
**Mental Model**: Think of a reducer as part of the store. It should have no side effects and only define how data changes from one state to the next given action messages.
|
||||
|
||||
A reducer is a **pure function** that receives some state and an action message as inputs and generates a copy of modified state as the output. Redux manages state changes mainly through reducers, and they are directly related to the UI, so for this exercise, we'll have to use jest tests to see the inner workings.
|
||||
### Advanced: Middleware
|
||||
|
||||
From the official documentation site:
|
||||
From the [documentation site](https://redux.js.org/advanced/middleware):
|
||||
|
||||
> Reducers are just pure functions that take the previous state and an action, and return the next state. Remember to return new state objects, instead of mutating the previous state.
|
||||
> Redux middleware provides a third-party extension point between dispatching an action, and the moment it reaches the reducer.
|
||||
|
||||
**Mental Model**: think of Reducer as part of the store and should have no side effects outside of defining how data can change from one state to next given action messages.
|
||||
We won't be covering middleware much in these lessons since it's a more advanced topic.
|
||||
|
||||
# Getting Started with Redux
|
||||
## Getting started with Redux
|
||||
|
||||
We begin the journey into Redux by looking at the store. The store consists of several parts
|
||||
We begin the journey into Redux by looking at the store. The store consists of several parts:
|
||||
|
||||
1. the state or the data - we represent this both with an initial state and with a TypeScript interface.
|
||||
2. the reducer - responsible for reacting to action messages to change the state from a previous to the next state.
|
||||
3. the dispatcher - there is only one dispatcher and the store exports this. We'll look at this in Step 6!
|
||||
1. **State/data** - We represent this both with an initial state and with a TypeScript interface.
|
||||
2. **Reducers** - Responsible for reacting to action messages to change from a previous to the next state.
|
||||
3. **Dispatcher** - There should be only one dispatcher, which is exported by the store. We'll look at this in Step 6!
|
||||
|
||||
## Create Store
|
||||
### Create store
|
||||
|
||||
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.
|
||||
The `createStore()` function is provided by Redux and can take in several arguments. The simplest form just takes in reducers.
|
||||
|
||||
```ts
|
||||
const store = createStore(reducer);
|
||||
```
|
||||
|
||||
`createStore()` can take in an initial state - the initial state can come from any source. There are two use cases:
|
||||
`createStore()` can also take in an initial state. There are two common sources for the initial state:
|
||||
|
||||
1. load initial data from an API server
|
||||
2. load data that is generated from a server side rendering environment
|
||||
1. Load initial data from a server
|
||||
2. Load data that is generated by a server-side rendering environment
|
||||
|
||||
```ts
|
||||
const store = createStore(reducer, {
|
||||
|
@ -75,15 +79,13 @@ const store = createStore(reducer, {
|
|||
});
|
||||
```
|
||||
|
||||
`createStore()` also takes in a third argument that injects **middleware**. From the documentation site:
|
||||
`createStore()` can take a third argument that injects middleware, but we won't use this until later.
|
||||
|
||||
> [Redux Middleware] provides a third-party extension point between dispatching an action, and the moment it reaches the reducer.
|
||||
|
||||
## Reducers
|
||||
### Reducers
|
||||
|
||||
Remember that the reducers are **pure**. Pure functions have no side effects. They always return the same output given the same input (idempotent). They are easily testable.
|
||||
|
||||
Reducers' only job is to change the state from one snapshot to another. They are simple functions that take in the state and an action message. These reducers looks at the action message to decide what to do to the state. A convention that has been established in the Flux community is the `type` key in the action message. Another convention is using switch statements against the `type` key to trigger further reducer functions.
|
||||
Reducers look at the action's message to decide what to do to the state. A convention established in the Flux community is that the action message (payload) should include a `type` key. Another convention is using switch statements against the `type` key to trigger further reducer functions.
|
||||
|
||||
```ts
|
||||
function reducer(state: Store['todos'], payload: any): Store['todos'] {
|
||||
|
@ -96,14 +98,16 @@ function reducer(state: Store['todos'], payload: any): Store['todos'] {
|
|||
}
|
||||
```
|
||||
|
||||
In these demo and exercises, I separated out the pure & reducer functions to make it cleaner. The tests inside `pureFunctions.spec.ts` should describe the behavior of the individual functions. They are easy to follow and easy to write.
|
||||
In the demo and exercises for this step, I separated the pure and reducer functions into different files to make it cleaner. The tests inside `pureFunctions.spec.ts` should describe the behavior of the individual functions. They are easy to follow and easy to write.
|
||||
|
||||
# Exercise
|
||||
|
||||
1. First, take a look at the store interface in the `exercise/src/store/index.tsx` - note that the `Store` interface has two keys: `todos` and `filter`. We'll concentrate on the `todos` which is an object where the keys are IDs and the values are of an `TodoItem` type.
|
||||
If you still have the app running from a previous step, stop it with `ctrl+c`. Start the tests instead by running `npm test` from the root of the `frontend-bootcamp` folder.
|
||||
|
||||
2. Open `exercise/src/reducers/pureFunctions.ts` and fill in the missing body of the pure functions.
|
||||
1. First, take a look at the store interface in `exercise/src/store/index.ts`. Note that the `Store` interface has two keys: `todos` and `filter`. We'll concentrate on `todos`, which is an object where the keys are string IDs and the values are of a `TodoItem` type.
|
||||
|
||||
3. Open `exercise/src/reducers/index.ts` and fill in the missing case statements for the switch of `action.type`.
|
||||
2. Open `exercise/src/reducers/pureFunctions.ts` and fill in the missing function bodies.
|
||||
|
||||
3. Open `exercise/src/reducers/index.ts` and fill in the missing case statements for the switch on `action.type`.
|
||||
|
||||
4. Open `exercise/src/reducers/pureFunctions.spec.ts` and implement tests for the functions you wrote for `remove`, `complete`, and `clear`.
|
||||
|
|
Загрузка…
Ссылка в новой задаче