这是几个入门学习 React 的小 Demo,帮助自己学习了解 React 的运行机制,结合 React 官方文档,会更佳噢 …
DEMO 目录
- ReactDOM.render()
- Use Array in JSX
- 组件
- this.props.children
- PropTypes
- 获取真实的 DOM 节点
- this.state
- 表单
- 组件的生命周期
- 使用 Promise 获取 Github 的数据
- Todo List
- 井字棋(Tic Tac Toe)
引入资源
- With babel-standalone
<div id="output"></div>
<!-- Load Babel -->
<!-- v6 <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<!-- Your custom script here -->
<script type="text/babel">
const getMessage = () => "Hello World";
document.getElementById('output').innerHTML = getMessage();
</script>
Demo01: ReactDOM.render()
Demo / Source
初始化咱先 Hello 一下,使用 jsx 语法,碰到代码块使用({})包起来,碰到 html 标签,就使用(< />):
var names = ["AAA", "BBB", "CCC"];
ReactDOM.render(
<div>
{names.map(function(name) {return <h2>Hello, {name}!</h2>;
})}
</div>,
document.getElementById("example")
);
Demo02: Use Array in JSX
Demo / Source
如果 JavaScript 的变量是个数组,会展开这个数组的所有项.
var arr = [<h1 key="h1">Hello,</h1>, <h2 key="h2">React is awesome!</h2>];
ReactDOM.render(<div>{arr}</div>, document.getElementById("example"));
Demo03: 组件
Demo / Source
变量 HelloMsg 是一个组件类。模板插入 <HelloMsg /> 时,会自动生成 HelloMsg 的一个实例。所有组件类都必须有自己的 render 方法,用于输出组件。
class HelloMsg extends React.Component {render() {return <h1>Hello, {this.props.name}</h1>;
}
}
ReactDOM.render(
<HelloMsg name="Dataozi" />,
document.getElementById("example")
);
Demo04: this.props.children
Demo / Source
this.props
对象的属性与组件的属性一一对应,但是有一个例外,就是 this.props.children
属性。
ps: 注意大小写 React.Children
、React.Component
class NotesList extends React.Component {render() {
return (
<ol>
{React.Children.map(this.props.children, function(child) {return <li>{child}</li>;
})}
</ol>
);
}
}
ReactDOM.render(
<NotesList>
<span>Hello</span>
<span>World</span>
<span>React</span>
</NotesList>,
document.getElementById("example")
);
Demo05: PropTypes
Demo / Source
- 使用 PropTypes 进行类型检查
React 内置了一些类型检查的功能。要在组件的 props 上进行类型检查,你只需配置特定的 propTypes 属性:
var data = {
tilte: "Hello",
age: 19,
isStudent: true
};
class MyTitle extends React.Component {
static propTypes = {
tilte: PropTypes.string,
age: PropTypes.number,
isStudent: PropTypes.bool
};
render() {
return (
<div>
<h1>{this.props.data.tilte}</h1>
<h2>{this.props.data.age}</h2>
<h3>{this.props.data.isStudent ? "Yes" : "No"}</h3>
</div>
);
}
}
ReactDOM.render(<MyTitle data={data} />, document.getElementById("root"));
还可以通过配置特定的 defaultProps 属性来定义 props 的默认值:
class DefaultTitle extends React.Component {render() {return <h4>{this.props.title}</h4>;
}
}
// 指定 props 的默认值:DefaultTitle.defaultProps = {title: "Hello React!"};
ReactDOM.render(<DefaultTitle />, document.getElementById("root2"));
Demo06: 获取真实的 DOM 节点
Demo / Source
- Refs and the DOM
Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素。
- 创建 Refs: Refs 是由
React.createRef()
创建的,并通过 ref 属性附加到 React 元素(比如 input) - 访问 Refs: 当 ref 被传递给 render 中的元素时,对该节点的引用可以在 ref 的 current 属性中被访问,
this.myTextFocus.current.focus();
你不能在函数组件上使用 ref 属性,因为它们没有实例
组件 MyComponent 的子节点有一个文本输入框,用于获取用户的输入。这时就必须获取真实的 DOM 节点,虚拟 DOM 是拿不到用户输入的。
class MyComponent extends React.Component {constructor(props) {super(props);
// 创建一个 ref 来存储 myTextFocus 的 DOM 元素
this.myTextFocus = React.createRef();
this.handerClick = this.handerClick.bind(this);
}
handerClick() {
// 直接使用原生 API 使 text 输入框获得焦点
// 通过 "current" 来访问 DOM 节点
this.myTextFocus.current.focus();}
render() {
// 告诉 React 我们想把 <input> ref 关联到
// 构造器里创建的 `myTextFocus` 上
return (
<div>
<input type="text" ref={this.myTextFocus} />
<input type="button" value="点击聚焦" onClick={this.handerClick} />
</div>
);
}
}
ReactDOM.render(<MyComponent />, document.getElementById("root"));
Demo07: this.state
Demo / Source
- State & 生命周期
学习如何封装真正可复用的 Clock 组件。它将设置自己的计时器并每秒更新一次。
class Clock extends React.Component {constructor(props) {super(props);
this.state = {date: new Date() }; // 为 this.state 赋初值
}
componentDidMount() {
// Clock 初次被渲染到 DOM 时,为其挂载一个计时器
this.timerID = setInterval(() => this.tick(), 1000);
}
componentWillUnmount() {
// Clock 被删除时,卸载其计时器
clearInterval(this.timerID);
}
tick() {// 使用 this.setState() 来时刻更新组件 state
this.setState({date: new Date() });
}
render() {
return (
<div>
<h1>Hello, React!</h1>
<h2> 现在是北京时间:{this.state.date.toLocaleTimeString()}</h2>
</div>
);
}
}
ReactDOM.render(<Clock />, document.getElementById("root"));
Demo08: 表单
Demo / Source
- 表单
受控组件:渲染表单的 React 组件还控制着用户输入过程中表单发生的操作,被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”。
即:表单数据是由 React 组件来管理的。
class NameForm extends React.Component {constructor(props) {super(props);
this.state = {
// 唯一数据源
value: ""
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value // 显示的值将随着用户输入而更新});
}
handleSubmit(event) {if (this.state.value) {alert("接受到的 name 值是:" + this.state.value);
}
event.preventDefault();}
render() {
return (<form onSubmit={this.handleSubmit}>
<input
type="text"
value={this.state.value}
onChange={this.handleChange}
/>
<input type="submit" value="提交" />
</form>
);
}
}
ReactDOM.render(<NameForm />, document.getElementById("root"));
非受控组件:表单数据将交由 DOM 节点来处理,即使用 ref 来从 DOM 节点中获取表单数据
Demo / Source
class NameForm extends React.Component {constructor(props) {super(props);
this.input = React.createRef();
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {alert("接受到的 name 值是:" + this.input.current.value);
event.preventDefault();}
render() {
return (<form onSubmit={this.handleSubmit}>
<input type="text" ref={this.input} />
<input type="submit" value="提交" />
</form>
);
}
}
ReactDOM.render(<NameForm />, document.getElementById("root"));
Demo09: 组件的生命周期
Demo / Source
- 组件的生命周期
- 生命周期图谱速查表
- React 的生命周期 — Ant Design 语雀
主要路线顺序:挂载 – 更新 – 卸载 – 错误处理
挂载
当组件实例被创建并插入 DOM 中时,其生命周期调用如下:
- consctructor() — React 组件的构造函数,不初始化 state 或不进行方法绑定,则不需要
- static getDerivedStateFromProps() — 不常用
- render() — 唯一必须实现的方法,并且应该是纯函数
- componentDidMount() — 依赖于 DOM 节点的初始化应该在这里
更新
当组件的 props 或 state 发生变化时,会触发更新:
- static getDerivedStateFromProps()
- shouldComponentUpdate()
- render()
- getSnapshotBeforeUpdate() — 不常用
- componentDidUpdate() — 在更新后会被立即调用
卸载
当组件从 DOM 中移除时:
- componentWillUnmount() — 会在组件卸载及销毁之前直接调用
错误处理
当渲染过程,生命周期,或子组件的构造函数中抛出错误时:
- static getDerivedStateFromError()
- componentDidCatch()
过期的生命周期方法:
- UNSAFE_componentWillMount() — 挂载前调用,目前使用 constructor()初始化 state
- UNSAFE_componentWillReceiveProps()
- UNSAFE_componentWillUpdate()
class Hello extends React.Component {constructor(props) {super(props);
this.state = {
fontSize: 12,
opacity: 0.01
};
}
componentDidMount() {this.timerID = setInterval(() => {
let opacity = this.state.opacity;
let fontSize = this.state.fontSize;
opacity += 0.02;
fontSize += 1;
if (opacity >= 1) {opacity = 0.01;}
if (fontSize >= 63) {fontSize = 12;}
this.setState({
fontSize,
opacity
});
}, 100);
}
componentWillUnmount() {clearInterval(this.timerID);
}
render() {
return (
<h1
style={{opacity: this.state.opacity, fontSize: this.state.fontSize}}
>
Hello, {this.props.name}
</h1>
);
}
}
ReactDOM.render(<Hello name="React" />, document.getElementById("root"));
Demo10: 使用 Promise 获取 Github 的数据
Demo / Source
ReactDOM.render(
<ReportList
promise={$.getJSON("https://api.github.com/search/repositories?q=javascript&sort=stars")}
/>,
document.getElementById("root")
);
从 Github 的 API 抓取数据,然后将 Promise 对象作为属性,传给 ReportList 组件。
如果 Promise 对象正在抓取数据(pending 状态),组件显示 ”loading…”;
如果 Promise 对象报错(rejected 状态),组件显示报错信息;
如果 Promise 对象抓取数据成功(fulfilled 状态),组件显示获取的数据。
在这里查看完整 Demo/ 源码 — 谷歌浏览器有时候会报跨域的问题,可以使用火狐等浏览器试看
接下来来几个混合实战吧
Demo11: Todo List
Demo / Source
- React todo list
主要练习使用 props
和 state
,使用 state 保存现有的待办事项列表及用户的一些操作(删除、完成)等。
class TodoApp extends React.Component {constructor(props) {super(props);
this.state = {items: []
};
this.addItem = this.addItem.bind(this);
this.deleteItem = this.deleteItem.bind(this);
this.doneItem = this.doneItem.bind(this);
}
addItem(item) {
const newItem = {
text: item.text,
id: Date.now(),
done: false
};
this.setState({items: this.state.items.concat(newItem)
});
}
deleteItem(index) {this.state.items.splice(index, 1);
this.setState({items: this.state.items});
}
doneItem(index) {
const items = this.state.items;
const todo = items[index];
items.splice(index, 1);
todo.done = !todo.done;
todo.done ? items.unshift(todo) : items.push(todo);
this.setState({items});
}
render() {
return (
<div className="container">
<h1>TODO</h1>
<TodoList
items={this.state.items}
deleteClick={this.deleteItem}
doneClick={this.doneItem}
/>
<TodoForm addItem={this.addItem} items={this.state.items} />
</div>
);
}
}
Demo12: 井字棋(Tic Tac Toe)
Demo / Source
- Tic Tac Toe
- 井字棋游戏教程文档
- React 的井字过三关
tic-tac-toe(三连棋)游戏的功能
- [x] 能够判定玩家何时获胜
- [x] 能够记录游戏进程
- [x] 允许玩家查看游戏的历史记录,也可以查看任意一个历史版本的游戏棋盘状态
- [x] 在游戏历史记录列表显示每一步棋的坐标,格式为 (列号, 行号)
- [x] 在历史记录列表中加粗显示当前选择的项目
- [] 使用两个循环来渲染出棋盘的格子,而不是在代码里写死(hardcode)
- [] 添加一个可以升序或降序显示历史记录的按钮
- [] 每当有人获胜时,高亮显示连成一线的 3 颗棋子
- [x] 当无人获胜时,显示一个平局的消息
学习资料
- React 入门实例教程 — 阮一峰
- react-demos