React 是一个用于构建用户界面的 JavaScript 库,它只负责应用的视图层,帮助开发人员构建快速且交互式的 web 应用程序。
1 基本使用
1.1 创建虚拟 DOM:
React.createElement(component, props, ...children)
- JSX (第一种方式的语法糖) ```jsx // 1.创建虚拟DOM const VDOM1 = React.createElement( ‘h1’, { id: ‘title’ }, React.createElement(‘span’, {}, ‘Hello,React1’) );
const VDOM2 = (
Hello,React2
);// 2.渲染虚拟DOM ReactDOM.render(VDOM2, document.getElementById(‘root’));
虚拟DOM打印:<br />
<a name="nZCgs"></a>
## 1.2 JSX
1. 定义虚拟 DOM 时,不要写引号。
2. 标签中混入** JS 表达式**时要用`{}`。
3. 样式的类名指定不要用 class,要用 `className`。
4. 内联样式,要用 `style={{key:value}}` 的形式去写。
5. 只有一个根标签
6. 单标签必须闭合
7. 会自动展开数组
8. 标签首字母
1. 若小写字母开头,则将该标签转为 html 中同名元素,若 html 中无该标签对应的同名元素,则报错
2. 若大写字母开头,react 就去渲染对应的组件,若组件没有定义,则报错。
```jsx
const myId = '0001';
const myData = 'React';
const data = ['Angular', 'React', 'Vue'];
const VDOM3 = (
<div>
<h2 className="title" id={myId.toLowerCase()}>
<span style={{ color: 'red', fontSize: '29px' }}>
{myData.toUpperCase()}
</span>
</h2>
<ul>
{data.map((item, index) => {
return (
<li key={index}>
{index}:{item}
</li>
);
})}
</ul>
<input type="text" />
</div>
);
if 语句以及 for 循环不是 JavaScript 表达式,所以不能在 JSX 中直接使用 表达式:
- a
- a+b
- fn()
- arr.map()
- function test(){}
语句:
- if(){}
- for(){}
- switch(){}
1.3 组件化
- 组件名必须首字母大写
只能有一个根元素,必须有结束标签
函数式组件
function MyComponent1() {
//此处的this是undefined,因为babel编译后开启了严格模式
console.log(this);
return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>;
}
类式组件
class MyComponent2 extends React.Component {
render() {
//render是放在哪里的?—— MyComponent的原型对象上,供实例使用。
//render中的this是谁?—— MyComponent的实例对象 <=> MyComponent组件实例对象。
console.log('render中的this:', this);
return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>;
}
}
渲染类组件标签的基本流程
创建组件实例对象,调用 render 得到虚拟DOM,解析为真实DOM,插入到页面
- 渲染组件
ReactDOM.render(<MyComponent/>, document.getElementById('root'));
1.4 组件实例三大属性
state
state 是组件对象最重要的属性, 值是对象(可以包含多个 key-value 的组合),不能直接修改或更新
- 组件中 render 方法中的 this 为组件实例对象
组件自定义的方法中 this 为 undefined,如何解决?
- 强制绑定 this: 通过函数对象的 bind()
箭头函数
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// 为了在回调中使用 `this`,
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
使用箭头函数 ```jsx export default class Toggle extends React.Component { state = { isHot: false, wind: ‘微风’ };
// 箭头函数 changeWeather = () => { const isHot = this.state.isHot; this.setState({ isHot: !isHot }); }; render() { const { isHot, wind } = this.state; return (
<h1 onClick={this.changeWeather}>
今天天气很{isHot ? '炎热' : '凉爽'},{wind}
</h1>
); } }
this指向问题:
```jsx
class Cat {
sayThis () {
console.log(this); // 这里的 `this` 指向谁?
}
exec (cb) {
cb(); // 回调函数 没有显示调用者指向 undefined
}
render () {
this.exec(this.sayThis);
}
}
const tom = new Cat();
tom.render(); // undefined
// ES6 的 class 语法,自动地使用严格模式,非严格模式下指向全局对象
// 当你使用 onClick={this.handleClick}来绑定事件监听函数的时候,
// handleClick 函数实际上会作为回调函数,传入 addEventListener()
class Demo {
constructor() {
this.name = 'demo';
}
handleClick1 = () => {
console.log(this.name);
};
handleClick2() {
console.log(this.name);
}
}
const demo = new Demo();
const fn1 = demo.handleClick1;
const fn2 = demo.handleClick2;
console.log(demo.handleClick1()); // demo
console.log(demo.handleClick2()); // demo
console.log(fn1()); // demo
console.log(fn2()); // this undefined
class 中的方法如果是普通函数方法,该方法会绑定在构造函数的原型上 但是如果方式是箭头函数方法,该方法会绑定实例上
props
- 通过标签属性从组件外向组件内传递变化的数据
- 注意: 组件内部不要修改 props 数据,单向数据流 ```jsx import React from ‘react’;
export default class Person extends React.Component { constructor(props) { //构造器是否接收props,是否传递给super,取决于是否希望在构造器中通过this访问props super(props); console.log(‘constructor’, this.props); }
//对标签属性进行类型、必要性的限制 static propTypes = { name: PropTypes.string.isRequired, //限制name必传,且为字符串 sex: PropTypes.string, //限制sex为字符串 age: PropTypes.number, //限制age为数值 };
//指定默认标签属性值 static defaultProps = { sex: ‘男’, //sex默认值为男 age: 18, //age默认值为18 };
render() { const { name, age, sex } = this.props; //props是只读的 return (
- 姓名:{name}
- 性别:{sex}
- 年龄:{age + 1}
//渲染组件到页面
ReactDOM.render(
对 props 中的属性值进行类型限制和必要性限制:
- ~~propTypes ~~方式已弃用
```jsx
Person.propTypes = {
name: React.PropTypes.string.isRequired,
age: React.PropTypes.number
}
- 使用 prop-types 库进限制(需要引入 prop-types 库)
import PropTypes from 'prop-types';
Person.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number.
}
refs
refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素。
React.createRef()
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return <div ref={this.myRef} />;
}
}
回调形式
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
this.textInput = null;
this.setTextInputRef = element => {
this.textInput = element;
};
}
render() {
// 使用 `ref` 的回调函数将 text 输入框 DOM 节点的引用存储到 React
// 实例上(比如 this.textInput)
return (
<div>
<input
type="text"
ref={this.setTextInputRef}
/>
</div>
);
}
}
如果 ref 回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 null,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。
String 类型的 Refs
例如ref="textInput"
。可以通过this.refs.textInput
来访问 DOM 节点。不建议使用它,因为 string 类型的 refs 存在 一些问题。它已过时并可能会在未来的版本被移除。
事件处理
- 通过
onXxx
属性指定事件处理函数(注意大小写)
- React 使用的是自定义(合成)事件, 而不是使用的原生DOM事件 —————— 为了更好的兼容性
- React 中的事件是通过事件委托方式处理的(委托给组件最外层的元素) ————————为了的高效
- 通过 event.target 得到发生事件的DOM元素对象 ——————————不要过度使用ref
1.5 受控组件
在一个受控组件中,表单数据是由 React 组件来管理的。另一种替代方案是使用非受控组件,这时表单数据将交由 DOM 节点来处理。
非受控组件
class Login extends React.Component{
handleSubmit = (event)=>{
event.preventDefault() //阻止表单提交
const {username,password} = this
alert(`你输入的用户名是:${username.value},你输入的密码是:${password.value}`)
}
render(){
return(
<form onSubmit={this.handleSubmit}>
用户名:<input ref={c => this.username = c} type="text" name="username"/>
密码:<input ref={c => this.password = c} type="password" name="password"/>
<button>登录</button>
</form>
)
}
}
受控组件
class Login extends React.Component {
//初始化状态
state = {
username: '', //用户名
password: '' //密码
}
//保存用户名到状态中
saveUsername = (event) => {
this.setState({ username: event.target.value })
}
//保存密码到状态中
savePassword = (event) => {
this.setState({ password: event.target.value })
}
//表单提交的回调
handleSubmit = (event) => {
event.preventDefault() //阻止表单提交
const { username, password } = this.state
alert(`你输入的用户名是:${username},你输入的密码是:${password}`)
}
render() {
return (
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={this.saveUsername} type="text" name="username" />
密码:<input onChange={this.savePassword} type="password" name="password" />
<button>登录</button>
</form>
)
}
}
2 声明周期
生命周期的三个阶段(旧)
1.初始化阶段: 由 ReactDOM.render()
触发—-初次渲染
- constructor()
componentWillMount()- render()
- componentDidMount()
2.更新阶段: 由组件内部this.setSate()
或父组件重新 render 触发
a. shouldComponentUpdate()
b. ~~componentWillUpdate() ~~ // 组件将要更新的钩子
c. render()
d. componentDidUpdate() // 组件更新完毕的钩子
- 卸载组件: 由
ReactDOM.unmountComponentAtNode()
触发
a. componentWillUnmount()
生命周期的三个阶段(新)
1. 初始化阶段: 由
ReactDOM.render()
触发—-初次渲染
a. constructor()
b. getDerivedStateFromProps 若state的值在任何时候都取决于props,那么可以使用
c. render()
d. componentDidMount() : 组件挂载完毕的钩子,一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
更新阶段: 由组件内部
this.setSate()
或父组件重新 render 触发
a. getDerivedStateFromProps
b. shouldComponentUpdate() 控制组件更新的“阀门”
c. render()
d. getSnapshotBeforeUpdate 在更新之前获取快照
c. componentDidUpdate() 组件更新完毕的钩子卸载组件: 由
ReactDOM.unmountComponentAtNode()
触发componentWillUnmount(): 组件将要卸载的钩子,一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息
shouldComponentUpdate(){
console.log('Count---shouldComponentUpdate');
return true
}
重要的勾子:
- render:初始化渲染或更新渲染调用
- componentDidMount:开启监听, 发送 ajax 请求
- componentWillUnmount:做一些收尾工作, 如: 清理定时器
即将废弃的勾子:
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
3 setState
setState(stateChange, [callback])-
——-对象式的setState
- stateChange为状态改变对象(该对象可以体现出状态的更改)
callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用
<br />`setState(updater, [callback])-`-----函数式的setState
updater 为返回stateChange对象的函数。
- updater 可以接收到state和props。
- callback 是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。
总结:
1.对象式的setState是函数式的setState的简写方式(语法糖)
2.使用原则:
(1).如果新状态不依赖于原状态 ===> 使用对象方式
(2).如果新状态依赖于原状态 ===> 使用函数方式
(3).如果需要在setState()执行后获取最新的状态数据,要在第二个callback函数中读取
4 Hooks
Hooks 是React 16.8.0版本增加的新特性/新语法,可以让你在函数组件中使用 state 以及其他的 React 特性
三个常用的Hook
- State Hook:
React.useState()
- Effect Hook:
React.useEffect()
- Ref Hook:
React.useRef()
State Hook
- State Hook让函数组件也可以有 state 状态, 并进行状态数据的读写操作
- 语法:
const [xxx, setXxx] = React.useState(initValue)
useState()
说明:参数: 第一次初始化指定的值在内部作缓存<br /> 返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
setXxx()
2种写法:setXxx(newValue)
: 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值setXxx(value => newValue)
: 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值
Effect Hook
Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
React中的副作用操作:
- 发ajax请求数据获取
- 设置订阅 / 启动定时器
手动更改真实DOM
useEffect(() => {
// 在此可以执行任何带副作用操作
return () => { // 在组件卸载前执行
// 在此做一些收尾工作, 比如清除定时器/取消订阅等
}
}, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
可以把 useEffect Hook 看做如下三个函数的组合:
componentDidMount()
- componentDidUpdate()
- componentWillUnmount()
Ref Hook
- Ref Hook 可以在函数组件中存储/查找组件内的标签或任意其它数据
- 语法:
const refContainer = useRef()
- 作用:保存标签对象,功能与
React.createRef()
一样
5 组件通信方式总结
几种通信方式:
- props:
- children props
- render props
- 消息订阅-发布:pubs-sub、event等等
- 集中式管理:redux、dva等等
- context: 生产者-消费者模式
最佳实践:
- 父子组件:props
- 兄弟组件:消息订阅-发布、集中式管理
- 祖孙组件(跨级组件):消息订阅-发布、集中式管理、context(开发用的少,封装插件用的多)
6 路由
下载:npm install react-router-dom
5.1.1 路由基本使用
// App.js
import React from 'react';
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
function Index() {
return <div>首页</div>;
}
function News() {
return <div>新闻</div>;
}
function App() {
return (
<Router>
<div>
<Link to="/index">首页</Link>
<Link to="/news">新闻</Link>
</div>
<div>
<Route path="/index" component={Index}/>
<Route path="/news" component={News}/>
</div>
</Router>
);
}
5.1.2 路由嵌套
function News(props) {
return (
<div>
<div>
<Link to={`${props.match.url}/company`}>公司新闻</Link>
<Link to={`${props.match.url}/industry`}>行业新闻</Link>
</div>
<div>
<Route path={`${props.match.path}/company`} component={CompanyNews} />
<Route path={`${props.match.path}/industry`} component={IndustryNews}/>
</div>
</div>
);
}
function CompanyNews() {
return <div>公司新闻</div>
}
function IndustryNews() {
return <div>行业新闻</div>
}
5.1.3 路由传参
import url from 'url';
class News extends Component {
constructor(props) {
super(props);
this.state = {
list: [{
id: 1,
title: '新闻1'
}, {
id: 2,
title: '新闻2'
}]
}
}
render() {
return (
<div>
<div>新闻列表组件</div>
<ul>
this.state.list.map((item, index) => {
return (
<li key={index}>
<Link to={`/detail?id=${item.id}`}>{item.title}</Link>
</li>
);
})
</ul>
</div>
);
}
}
class Detail extends Component {
constructor(props) {
super(props);
}
const { query } = url.parse(this.props.location.search, true);
console.log(query); // {id: 1}
render() {
return <div>新闻详情</div>
}
}
5.1.4 路由重定向
import { Redirect } from 'react-router-dom';
class Login extends Component {
render() {
if (this.state.isLogin) {
return <Redirect to="/"/>
}
}
}