React 简单入门

React 简单入门


目标:

  • 类比vue入门 react
  • 简单入门,着重react使用方法
  • 教程暂时不议论 redux

安装

1
2
3
npx create-react-app my-app
cd my-app
npm start

注意:
第一行的 npx 不是拼写错误 —— 它是 npm 5.2+ 附带的 package 运行工具

安装结束之后,的 app.js , 一起见识一下 JSX

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import logo from './logo.svg';
import './App.css';

function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}

export default App;

认识 JSX

1
const element = <h1>Hello, world!</h1>;
1
2
3
4
5
6
const element = (
<div>
<h1>Hello!</h1>
<h2>Good to see you here.</h2>
</div>
);

按照管官方的话说:既不是字符串也不是 HTML

  • React 认为渲染逻辑本质上与其他 UI 逻辑内在耦合
  • 在 JavaScript 代码中将 JSX 和 UI 放在一起时,会在视觉上有辅助作用
  • eact DOM 在渲染所有输入内容之前,默认会进行转义, 可以有效地防止 XSS(cross-site-scripting, 跨站脚本)攻击,防止注入攻击

1. 在 JSX 中, 可以在大括号内放置任何有效的 JavaScript 表达式

1
2
3
4
5
6
const name = 'lxl';
const element = (
<h1>
Hello, {name}!
</h1>
);

2. JSX 特定属性

  • 使用引号,来将属性值指定为字符串字面量

    1
    const element = <div tabIndex="0"></div>;
  • 使用大括号,来在属性值中插入一个 JavaScript 表达式

    1
    const element = <img src={user.avatarUrl}></img>;

    注意:

    • 在属性中嵌入 JavaScript 表达式时,不要在大括号外面加上引号。你应该仅使用引号(对于字符串值)或大括号(对于表达式)中的一个,对于同一属性不能同时使用这两种符号
    • 因为 JSX 语法上更接近 JavaScript 而不是 HTML,所以 React DOM 使用 camelCase(小驼峰命名)来定义属性的名称,而不使用 HTML 属性名称的命名约定,例如:data-id 写为 dataId
    • JSX 里的 class 是个关键字,所以使用 className 代替 class。

JSX 原理

简单来说就是对象,和vue的虚拟DOM一样,你写的想模板的jsx代码会转换成类似下边的结构

1
2
3
4
5
6
7
8
// 注意:这是简化过的结构
const element = {
type: 'h1',
props: {
className: 'greeting',
children: 'Hello, world!'
}
};

Babel 会把 JSX 转译成一个名为 React.createElement() 函数调用

等效的写法

1
2
3
4
5
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);
1
2
3
4
5
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);

函数组件与 class 组件

函数组件

最先安装好 react 后的 app.js,就是一个简单的函数组件,本质上就是一个函数,同样也能接收参数

1
2
3
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}

class 组件

上文说的 jsx 特性里 ‘JSX 里的 class 是个关键字,所以使用 className 代替 class’,是有点误解,事实上 class 是 ES6 的关键词,声明一个 :

1
2
3
4
5
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}

State

和vue一样,react也需要存储很多状态,通常是一些页面需要渲染的数据、响应页面元素的变量等,react的状态需要 __state__管理(高阶可以结合redux):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class UserInfo extends React.Component {
constructor(props) {
super(props);
this.state = {name: 'lxl', age: 18};
}

render() {
return (
<div>
<h1>User Info</h1>
<h2>name: {this.state.name}.</h2>
<h2>age: {this.state.age}.</h2>
</div>
);
}
}

与vue不同,state并没有被组件或者对象双向绑定,无法响应式的直接‘this.state.name = ‘www’’,设置name,这样是错误的,需要调用 setState:

1
2
3
this.setState({
date: new Date()
});

this.setState({
date: new Date()
});

数据自顶向下流动

引用一下菜鸟的文字:

  • 父组件或子组件都不能知道某个组件是有状态还是无状态,并且它们不应该关心某组件是被定义为一个函数还是一个类。这就是为什么状态通常被称为局部或封装。 除了拥有并设置它的组件外,其它组件不可访问。
  • 任何状态始终由某些特定组件所有,并且从该状态导出的任何数据或 UI 只能影响树中下方的组件

生命周期

React v16.3之前组件的生命周期可分成三个状态:

  • Mounting:已插入真实 DOM
  • Updating:正在被重新渲染
  • Unmounting:已移出真实 DOM
| 生命周期                  | 描述                                                         |
| ------------------------- | ------------------------------------------------------------ |
| componentWillMount        | 只会在装载之前调用一次,在 `render` 之前调用,你可以在这个方法里面调用 `setState` 改变状态,并且不会导致额外调用一次 `render` |
| componentDidMount         | 只会在装载完成之后调用一次,在 `render` 之后调用,从这里开始可以通过 `ReactDOM.findDOMNode(this)` 获取到组件的 DOM 节点。以在这个方法中调用setTimeout, setInterval或者发送AJAX请求等操作(防止异步操作阻塞UI)。 |
| componentWillReceiveProps | 更新组件触发,在组件接收到一个新的 prop (更新后)时被调用。这个方法在初始化render时不会被调用。 |
| shouldComponentUpdate     | 更新组件触发,返回一个布尔值。在组件接收到新的props或者state时被调用。在初始化时或者使用forceUpdate时不被调用。<br/>可以在你确认不需要更新组件时使用。 |
| componentWillUpdate       | 更新组件触发,在组件接收到新的props或者state但还没有render时被调用。在初始化时不会被调用。 |
| componentDidUpdate        | 更新组件触发在组件完成更新后立即调用。在初始化时不会被调用。 |
| componentWillUnmount      | 卸载组件触发,在组件从 DOM 中移除之前立刻被调用。            |

React v16.3 之后,引入了两个新的生命周期函数:

  • getDerivedStateFromProps
  • getSnapshotBeforeUpdate

同时deprecate了一组生命周期API,包括:

  • componentWillReceiveProps
  • componentWillMount
  • componentWillUpdate

看例子,userInfo。

事件处理

事件处理上和 DOM 元素的很相似,不同的是:

  • React 事件的命名采用小驼峰式(camelCase),而不是纯小写。
    1
    2
    3
    <button onClick={activateLasers}>
    Activate Lasers
    </button>
    onBlur、onInput …
  • 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。
1
2
3
4
5
6
7
8
9
10
11
12
function ActionLink() {
function handleClick(e) {
e.preventDefault();
console.log('The link was clicked.');
}

return (
<a href="#" onClick={handleClick}>
Click me
</a>
);
}

来自官方忠告:

你必须谨慎对待 JSX 回调函数中的 this,在 JavaScript 中,class 的方法默认不会绑定 this。如果你忘记绑定 this.handleClick 并把它传入了 onClick,当你调用这个函数的时候 this 的值为 undefined。
这并不是 React 特有的行为;这其实与 JavaScript 函数工作原理有关。通常情况下,如果你没有在方法后面添加 (),例如 onClick={this.handleClick},你应该为这个方法绑定 this。

如果觉得使用 bind 很麻烦,这里有两种方式可以解决。

    1. 如果你正在使用实验性的 public class fields 语法,你可以使用 class fields 正确的绑定回调函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class LoggingButton extends React.Component {
    // 此语法确保 `handleClick` 内的 `this` 已被绑定。
    // 注意: 这是 *实验性* 语法。
    handleClick = () => {
    console.log('this is:', this);
    }

    render() {
    return (
    <button onClick={this.handleClick}>
    Click me
    </button>
    );
    }
    }
    1. 如果你没有使用 class fields 语法,你可以在回调中使用箭头函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class LoggingButton extends React.Component {
    handleClick() {
    console.log('this is:', this);
    }

    render() {
    // 此语法确保 `handleClick` 内的 `this` 已被绑定。
    return (
    <button onClick={() => this.handleClick()}>
    Click me
    </button>
    );
    }
    }

注意:
此语法问题在于每次渲染 LoggingButton 时都会创建不同的回调函数。在大多数情况下,这没什么问题,但如果该回调函数作为 prop 传入子组件时,这些组件可能会进行额外的重新渲染。我们通常建议在构造器中绑定或使用 class fields 语法来避免这类性能问题

条件渲染

  • 可以直接三目运算符:

    1
    2
    3
    4
    5
    6
    7
    8
    render() {
    const showUser = false;
    return (
    <div>
    {showUser ? <UserInfo userData={userData}/> : <span>啥也没有</span>}
    </div>
    )
    }
  • 可以if else:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

toggleUserInfo () {
const showUser = false;
if (showUser) {
return <UserInfo />;
} else {
return null
}
}
render() {
return (
<div>
{toggleUserInfo}
</div>
)
}
  • 运算符也是可以的
    1
    2
    3
    4
    5
    6
    7
    8
    render() {
    const showUser = false;
    return (
    <div>
    {showUser && <UserInfo userData={userData}/>}
    </div>
    )
    }

没有做不到,只有想不到

列表渲染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
render () {
const links = [
{'name': 'Blog', icon:'iconbiji', linkUrl: 'https://blog.magicyou.cn/'},
{'name': 'Cloud', icon:'iconwenjianjia', linkUrl: ''},
{'name': 'Frp', icon:'icondiannao', linkUrl: ''},
{'name': 'Pi', icon:'iconcaomeigan', linkUrl: 'http://pi.magicyou.cn/'},
];

<Row>
{links.map((item, index) => (
<Col className="gutter-row" span={6} key={index}>
<p className="height-100">{ item.name }</p>
</Col>
))}
</Row>
}

状态提升

在 React 中,将多个组件中需要共享的 state 向上移动到它们的最近共同父组件中,便可实现共享 state,这就是所谓的“状态提升”。

查看温度换算例子

包含关系

有些组件无法提前知晓它们子组件的具体内容。在 Sidebar(侧边栏)和 Dialog(对话框)等展现通用容器(box)的组件中特别容易遇到这种情况

查看组合组件弹窗示例

路由

http://react-guide.github.io/react-router-cn/docs/guides/basics/RouteConfiguration.html