# Hello
install
安装过程可以 Ctrl + C 停止,用 cnpm 或 npm 来替代脚手架的 yarn 安装
# 安装脚手架
yarn global add create-react-app
# 创建react项目
npx create-react-app myapp
hello
import React from 'react';
function App() {
return (
<h1>Hello world!</h1>
);
}
export default App;
释放配置
默认配置被隐藏,如果希望显示在项目中(除非对webpack非常熟悉,否则不建议)
npm run eject
其他辅助脚本
# 构建项目
npm run build
yarn build
# 运行测试
npm run test
yarn test
# 另外一种构建方式
# required npm 6.0+
npm init react-app my-app
yarn create react-app my-app
# JSX
介绍
- JSX语法是为了简化React的语法
- JSX防注入攻,在渲染所有输入内容之前默认会转义(有效防XSS跨站脚本攻击)
注意事项
1、只能有一个根标签
2、单标签(img/input)必须闭合
3、不能使用for,class等关键字
使用方式
使用变量
const name = `React`;
function App() {
return (
<h1>Hello {name}!</h1>
);
}
使用函数
const Hello = () => {
return `Hello React!`;
}
function App() {
return (
<h1>{Hello()}</h1>
);
}
使用map
<ul>
{['React', 'Vue'].map(item => <li>{item}</li>)}
</ul>
直接数组会跟字符串相同
<h1>{['React', 'Vue']}</h1> // 输出 ReactVue
# 组件
基本用法
组件名必须大写以区分函数和方法
函数组件(又名无状态组件:Hook出现以后取消无状态组件的叫法)
const Hello = () => <h1>Hello React!</h1>;
function App() {
return (
<Hello></Hello>
);
}
有状态组件(Hook出现后几乎不用)
class Hello extends React.Component {
render() {
return <h1>Hello React!</h1>;
}
}
function App() {
return (
<Hello></Hello>
);
}
如果没有插槽(Vue叫法,React叫组合),建议写成单标签
写成多标签等于告诉其他开发者,这个组件可以使用插槽用法
const Hello = () => <h1>Hello React!</h1>;
function App() {
return (
<Hello />
);
}
组件传参
除字符串外,必须用大括号包裹
const info = "React";
function App() {
const Hello = (props) => <h1>Hello {props.info}!</h1>;
return (
<Hello info={info} />
);
}
数据流动的概念:
- 组件可以把自己或父组件的状态传给子组件
- 这通常被称为自顶向下或单向数据流
参数只读
函数不更改传入的参数被称为纯函数
function sum(a, b) {
return a + b;
}
函数能改变自身参数的,被称为非纯函数
function withdraw(account, amount) {
account.total -= amount;
}
React必须像纯函数一样,不能更改参数的值
也就是说,子组件无法更新父组件的状态,只能让父组件自更新
# 生命周期
本章不重要
Hook出现之前,只有有状态组件才有生命周期
包含
实例化阶段
存在期阶段
销毁期阶段
实例化阶段
defaultProps:也就是初始化constructor的props参数
class App extends React.Component {
static defaultProps = {
name: "App"
}
}
constructor:主要初始化状态
class App extends React.Component {
constructor(props) {
super(props);
}
}
componentWillMount:最后一次修改状态的机会(很少用)
class App extends React.Component {
componentWillMount() {
}
}
render:渲染组件
class App extends React.Component {
render() {
return <h1>Hello world!</h1>
}
}
componentDidMount:只执行一次,可获取DOM。ajax请求也一般放在这里
class App extends React.Component {
componentDidMount() {
}
}
graph LR
defaultProps-->Constructor
Constructor-->componentWillMount
componentWillMount-->render
render-->componentDidMount
存在期阶段
componentWillReceiveProps:组件发生时改变
shouldComponentUpdate:控制组件重新渲染
componentWillUpdate:组件更新前
render:渲染组件
componentDidUpdate:组件渲染结束
销毁期
组件销毁时执行的生命周期,用于停止定时器,DOM解绑,取消订阅等
class App extends React.Component {
componentWillUnmount() {
}
}
# state
本章非重要
Hook出现之前,只有有状态组件才状态管理
使用
使用this.state使用状态
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
date: new Date().toLocaleTimeString()
}
}
render() {
return <div>Hello React!{this.state.date}</div>
}
}
变量接收
render() {
const date = this.state.date;
return <div>Hello React!{date}</div>
}
不能直接更新state
// 错误示范
this.state.comment = 'Hello';
更新状态
使用setState更新状态
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
date: new Date().toLocaleTimeString()
}
}
componentDidMount() {
setInterval(() => {
this.setState({
date: new Date().toLocaleTimeString()
});
}, 1000);
}
render() {
const date = this.state.date;
return <div>Hello React!{date}</div>
}
}
setState 不要直接使用state,否则不更新视图
setState 会合并原有的状态
// state = {
// name: "张三",
// age: 12
// }
// 不会把age属性丢失
this.setState({
name: "李四"
});
# 事件
事件
事件this默认指向window
解决方法一:constructor中绑定
constructor(props) {
super(props);
this.state = {
count: 1
}
this.add = this.add.bind(this);
}
解决方法二:回调函数(有性能问题)
<button onClick={() => this.add.bind(this)}>+ 1</button>
解决方案三:最新的ES语法,暂时在stage3(脚手架支持)
add = () => {
console.log(this);
}
事件对象
回调函数方式
add = (e, id) => {
console.log(e);
}
render() {
return <button onClick={(e) => this.add(e, `6`)}>+1</button>
}
bind方式
add = (id, e) => {
console.log(e);
}
render() {
return <button onClick={this.add.bind(this, `6`)}>+1</button>
}
阻止默认事件
button不写type默认是submit
React的阻止默认事件不能用return false
add = (id, e) => {
e.preventDefault();
}
# 条件渲染
if
渲染DOM
render() {
const login = true;
if (login) return <div>欢迎您</div>
else return <div>请登录</div>
}
渲染组件
render() {
const login = true;
if (login) return <Hello />
else return <Role />
}
抽取变量
render() {
const login = true;
const View = login ? <Hello /> : <Role />;
return <div>{View}</div>
}
运算符
render() {
const login = true;
return <div>{login && <Hello />}</div>
}
三目运算符
render() {
const login = true;
return <div>{login ? <Hello /> : <Role />}</div>
}
阻止组件渲染
组件返回null不会被渲染
const Hello = ({ show }) => {
return show ? <div>欢迎您</div> : null
}
# 遍历
示例
function App() {
let name = [1, 2, 3];
return <div>{name.map(item => {
return <p>{item * 2}</p>
})}</div>
}
可以提取变量,增强阅读性
function App() {
let name = [1, 2, 3];
const nameItem = name.map(item => {
return <p>{item * 2}</p>
})
return <div>{nameItem}</div>
}
keys
帮助识别DOM中某些元素的变化,提高diff算法的效率
一个元素的Key这个元素的列表中独一无二
const todoItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
);
元素没有id可以用索引值代替
const todoItems = todos.map((todo, index) =>
<li key={index}>
{todo.text}
</li>
);
key必须放在最外层的组件或DOM中
const todoItems = todos.map((todo) =>
<ListItem key={todo.id}></ListItem>
);
key在兄弟中唯一在全局不唯一
React遍历的key值无法通过组件传递
# 状态提升
非重要
Hook之后,都是用的函数组件
原理
多个子组件公用一套状态时,把这个状态提升到子组件公用的最近的父组件中
const ButtonMinus = (props) => {
return (
<>
<h2>当前值:{props.coumt}</h2>
<button onClick={props.change}>-1</button>
</>
)
}
const ButtonAdd = (props) => {
return (
<>
<h2>当前值:{props.coumt}</h2>
<button onClick={props.change}>+1</button>
</>
)
}
以上两个组件公用Change方法和count属性,提取到父组件中
# 组合继承
继承
React不推荐使用继承的方式来实现代码的重用
也很难实现,因为有状态组件都继承了React.Component,不能继承其他类
无状态组件继承其他类,那么其他类必须也是函数类型的,所以实现起来非常麻烦
组合
props.children访问组件中间的内容
const Button = (props) => {
return props.children;
}
function App() {
return <Button>确认</Button>
}
对未知容器的渲染非常友好
const Dialog = (props) => {
return <div className="dialog">{props.children}</div>;
}
function App() {
return (
<Dialog>
<p className="title">提示</p>
<p className="content">密码错误!</p>
</Dialog>
)
}
组件不经可以传递值,还能传递组件
<Dialog
left={<Hello />}
right={<Kugou />}
/>
特例关系
即对已有的的组件封装特殊的实例
const Dialog = (props) => {
return (
<div className="dialog">
<p className="title">{props.title}</p>
<p className="content">{props.content}</p>
</div>
)
}
const Error404Dialog = () => <Dialog title="错误提示" content="404" />
# 导入拓展
拓展
React的webpack配置默认不支持SCSS和LESS,如果想要使用,需要自行安装
React有个很好的机制,一引即用,很多插件无须配置
yarn add node-sass
yarn add bootstrap
React有一个动画库非常有名 react-flip-move 官网
flip是一种做动画的思维,react-flip-move就是以此命名
yarn add react-flip-move
官方快速入手DEMO
/**
* BEFORE:
*/
const TopArticles = ({ articles }) => (
{articles.map(article => (
<Article key={article.id} {...article} />
))}
);
/**
* AFTER:
*/
import FlipMove from 'react-flip-move';
const TopArticles = ({ articles }) => (
<FlipMove>
{articles.map(article => (
<Article key={article.id} {...article} />
))}
</FlipMove>
);
插件推荐
谷歌浏览器拓展:React Developer Tools
安装以后点击Component可以清楚的看到React的状态及props
# 表单
受控组件
React的输入框,下拉框,单选多选框绑定状态以后都变得无效,只能通过setState来更新
constructor(props) {
super(props);
this.state = {
name: ""
}
}
handleChange = (e) => {
this.setState({ name: e.target.value });
}
render() {
return <input type="text" value={this.state.name} onChange={this.handleChange.bind(this)} />;
}
如果想通过一个事件控制多个表单,前提是表单的name属性与state属性相同
handleChange = (e) => {
let state = {};
state[e.target.name] = e.target.value;
this.setState(state);
// ES6 新语法
// this.setState({[e.target.name],e.target.value});
}
select标签
render() {
return (
<select value={this.state.city} onChange={this.handleChange.bind(this)}>
<option value="101010200">海淀</option>
<option value="101010300">朝阳</option>
<option value="101010400">顺义</option>
</select>
)
}
非受控组件
表单可以不绑定状态,就属于非受控组件。需要使用它时,使用ref获取DOM值(具体Hook笔记)