Spring 2019 。 Ric Huang
import Counter from './containers/Counter';
ReactDOM.render(<Counter / >, document.getElementById('root'));
import Button from "../components/Button";
import Input from '../components/Input';
class Counter extends Component {
constructor(props) {
super(props);
this.state = { count: 100 };
}
... some methods for class Counter
render() {
return (
<div>
<h1>{this.state.count}</h1>
<span>
<Button text="+" onClick={this.handleInc} />
<Button text="-" onClick={this.handleDec} />
<Input onKeyPress={this.handleInput} />
</span>
</div>
);
}
}
// In Components/Button.js
import React from 'react'
export default ({ onClick, text }) => {
return <button onClick={onClick}>{text}</button>;
}
// In Components/Input.js
import React from 'react';
export default ({onKeyPress}) => {
return <input type="text"
placeholder="Enter a number..."
onKeyPress={onKeyPress}
/>;
}
class Counter extends Component {
...
handleInc = () => this.setState(state => ({ count: state.count + 1 }));
handleDec = () => this.setState(state => ({ count: state.count - 1 }));
setNumber = num => this.setState(() => ({ count: num }));
handleInput = e => {
if (e.key === "Enter") {
const value = parseInt(e.target.value);
if (value === 0 || value) this.setNumber(value);
e.target.value = "";
e.target.blur();
}
};
...
}
import classes from './TodoList.module.css'
import TodoItem from './TodoItem/TodoItem'
export default ({ items, toggleItem, removeItem, save }) => {
if (!items.length) return null;
return (
<ul className={classes.List}>
{items.map((ele, key) => (
<TodoItem item={ele} key={key} identity={key}
toggleItem={toggleItem}
removeItem={removeItem} save={save} / >
))}
</ul>
);
}
return (
<ul>{an array of React Components}</ul>
)
Array.map( element => (some JSX expression) );
來實現
<h1> This is a title </h1>
<p> This is a paragraph </p>
<div> This is a div </div>
<footer> This is a footer </footer>
class BlogPage extends Component {
render () {
return
<div className="blog-page">
{this.props.children}
</div>;
}
}
// In "App.js"
class App extends Component {
render() {
const contents = [];
contents.push(<h1> This is a title </h1>);
contents.push(<p> This is a paragraph </p>);
contents.push(<div> This is a div </div>);
contents.push(<footer> This is a footer </footer>);
return <BlogPage children={contents} />;
}
}
class MyTable extends Component {
render() {
return
<table>
<MyData dataInput={data1}/ >
<MyData dataInput={data2}/ >
</table>
}
}
class MyData extends Component {
render() {
return (
<div>
<tr>{some data}</tr>
<tr>{some data}</tr>
</div>
);
}
}
import React, { Fragment } from 'react';
class MyData extends Component {
render() {
return (
<Fragment>
<tr>{some data}</tr>
<tr>{some data}</tr>
</Fragment>
);
}
}
import React, { Fragment } from 'react';
class MyData extends Component {
render() {
return (
<>
<tr>{some data}</tr>
<tr>{some data}</tr>
</>
);
}
}
const generalNavBar = (WrappedNavBar, layoutMethod) =>
return class extends Component {
constructor(props) {...}
... some life-cycle methods or event handling logic
render() {
return <WrappedNavBar ... / >;
}
}
const withAuthGuard = WrappedComponent => {
return class extends Component {
render() {
return (
<Query query={ME_QUERY}
onError={error => {...; }}}> {
({ data, loading, error }) => {
if (loading) return <WrappedComponent loading={true} />
if (error) return <WrappedComponent isAuth={false} />
return (
<WrappedComponent isAuth={true} username={data.me.username} />)}
} </Query>)}
}
}
class App extends Component {
constructor(props) {
super(props);
this.textInput = React.createRef();
}
focusTextInput = () => { this.textInput.current.focus(); };
render() {
return (
<div>
<textarea rows="4" cols="50" ref={this.textInput} / >
<input type="button" value="Click to edit"
onClick={this.focusTextInput} />
</div>
);
}
}
class App extends Component {
constructor(props) {
super(props);
// 產生一個 React ref, 先把它存在 this.textInput 裡
this.textInput = React.createRef();
}
...
render() {
return (
<div>
// 產生一個 <textarea> element, 並且把存在
// this.textInput 的 reference 指到這個 element
<textarea ... ref={this.textInput} / >
...
</div>
);
}
}
class App extends Component {
...
render() {
return (
<div>
...
// 當這個 input button 被 click 的時候,
// 呼叫 this.focusTextInput
<input type="button" value="Click to edit"
onClick={this.focusTextInput} />
</div>
);
}
}
class App extends Component {
...
// 當 focusTextInput 被呼叫的時候,用 this.textInput.current
// 抓到目前被 referred 的 element, 也就是下面的 <textArea>,
// 並且執行它的 .focus()
focusTextInput = () => { this.textInput.current.focus(); };
render() {
return (
<div>
<textarea rows="4" cols="50" ref={this.textInput} / >
...
</div>
);
}
}
class App extends Component {
constructor(props) {
super(props);
this.textInput1 = React.createRef();
this.textInput2 = React.createRef();
}
...
render() {
return (
<div>
<textarea ... ref={this.textInput1} / >
<textarea ... ref={this.textInput2} / >
...
</div>
);
}
}
有時候必須將 local 拿到 child or HOC 的 component 來作為 ref, 以利在 local 做一些直接的操作
語法:
const SomeComponent = React.forwardRef(
(props, ref) => (<ChildComponent ref={ref} ... / >)
);
class App extends Component {
... this.ref = React.createRef();
// this.ref.current will become <ChildComponent>
render() {
return (
<SomeComponent ref={this.ref}... / >
);
}
}
const MyDiv = React.forwardRef((props, ref) => (
<div ref={ref} className="MyDiv">{props.children}</div>
));
class App extends Component {
constructor(props) {
super(props);
this.ref = React.createRef();
}
componentDidMount() { console.log(this.ref.current); }
render() {
return (
<MyDiv ref={this.ref}>
...
</MyDiv>
)}
}
學習完一些 advanced React topics 之後,
我們接下來要來學 React 生態系 裡頭一個很基礎,
也很重要的部分:React Router
import { BrowserRouter } from 'react-router-dom'
import { NavLink, Switch, Route } from 'react-router-dom'
class App extends Component {
render() {
return (
<BrowserRouter>
<div className="App">
<Blog / >
</div>
</BrowserRouter>
)
}
}
class Blog extends Component {
render() {
return (
<div> // Define your blog layout
...
<NavLink to="/home">Home</NavLink>
<NavLink to="/posts">Posts</NavLink>
<NavLink to="/authors">Authors</NavLink>
...
<Switch>
<Route exact path="/posts" component={Posts} / >
<Route path="/posts/:id?" component={PostRoute} / >
<Route exact path="/authors" components={Authors} / >
<Route path="/authors/:name?" components={AuthorRoute} / >
<Redirect from="/Home" to="/" / >
</Switch>
</div>
)
}
}
<Route path="/someDir" component={SomeComponent} / >
class Blog extends Component {
render() {
return (
<div> // Define your blog layout
<ul>
<li><NavLink to="/posts">Posts</NavLink></li>
<li><NavLink to="/authors">Authors</NavLink></li>
</ul>
<Switch>
<Route path="/posts" component={Posts} / >
<Route path="/authors" components={Authors} / >
</Switch>
</div>
)
}
}
<Route path="/posts/:id?" component={PostRender} / >
不過當這兩行同時存在的時候…
// 列舉所有文章
<Route path="/posts" component={Posts} / >
// 展示某篇文章
<Route path="/posts/:id?" component={PostRender} / >
當網址是 "…/posts/3838" 的時候,事實上兩條 routing rules 都會符合,所以照順序,會吐出第一個 match (列舉所有文章),而不是展示 3838 這篇文章
所以,第一條應該要改成 exact path:
<Route exact path="/posts" component={Posts} / >
<Redirect from="pathA" to="pathB" / >
Download "react-router-boilerplate.tgz" from Github!
* index.js // to mount "root" DOM node
* App.js // Define Routing root '/'
* containers/
* Blog/
* Blog.js // Define main page and routing rules
* Posts/
* Posts.js // List all posts (Posts module)
* PostRender.js // Define how to generate posts
* components/
* Post
* Post.js // Define Post module
export default class Posts extends Component {
render() {
const postIDs = ["1", "3", "5", "7", "9"];
const lists = postIDs.map((i, index) => (
<li key={index}>
<NavLink to={"/posts/" + i}>Posts #{i}</NavLink>
</li>));
return (
<div>
<h3>Click to view article ---</h3> {lists}
</div>);
}
}
export default class PostRender extends Component {
render() {
const postIDs = ["1", "3", "5", "7", "9"];
const { id } = this.props.match.params;
return id && postIDs.includes(id) ? (
<Post id={id} />
) : (
<div>
<h3>Error: Post #{id} NOT FOUND</h3>
</div>
);
}
}
(You are recommended to go through this official React Router Tutorial)