React 学习笔记
参考文章:React入门看这篇就够了
为什么要用React
- 1 使用
组件化
开发方式,符合现代 Web 开发的趋势 - 2 技术成熟,社区完善,配件齐全,适用于大型Web项目(生态系统健全)
- 3 由Facebook专门的团队维护,技术支持可靠
- 4 ReactNative - Learn once, write anywhere: Build mobile apps with React
- 5 使用方式简单,性能非常高,支持服务端渲染
- 6 React非常火,从技术角度,可以满足好奇心,提高技术水平;从职业角度,有利于求职和晋升,有利于参与潜力大的项目
React中的核心概念
- 1 虚拟DOM(Virtual DOM)
- 2 Diff算法(虚拟DOM的加速器,提升React性能的法宝)
虚拟DOM(Vitural DOM)
React 将 DOM 抽象为虚拟 DOM,虚拟 DOM 其实就是用一个对象来描述 DOM,通过对比前后两个对象的差异,最终只把变化的部分重新渲染,提高渲染的效率
为什么用虚拟 Dom,当 Dom 反生更改时需要遍历 而原生 Dom 可遍历属性多达231个 且大部分与渲染无关 更新页面代价太大
VituralDOM的处理方式
- 1 用 JavaScript 对象结构表示 DOM 树的结构,然后用这个树构建一个真正的 DOM 树,插到文档当中
- 2 当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异
- 3 把2所记录的差异应用到步骤1所构建的真正的 DOM 树上,视图就更新了
Diff算法
React创建组件的两种方式
- 1 通过 JS函数 创建(无状态组件)
- 2 通过 class 创建(有状态组件)
函数式组件 和 class 组件的使用场景说明:
1 如果一个组件仅仅是为了展示数据,那么此时就可以使用 函数组件
2 如果一个组件中有一定业务逻辑,需要操作数据,那么就需要使用 class 创建组件,因为,此时需要使用 state
JavaScript函数创建
- 注意:1 函数名称必须为大写字母开头,React通过这个特点来判断是不是一个组件
- 注意:2 函数必须有返回值,返回值可以是:JSX对象或
null
- 注意:3 返回的JSX,必须有一个根元素
- 注意:4 组件的返回值使用
()
包裹,避免换行问题
function Welcome(props) {
return (
// 此处注释的写法
<div className="shopping-list">
{/* 此处 注释的写法 必须要{}包裹 */}
<h1>Shopping List for {props.name}</h1>
<ul>
<li>Instagram</li>
<li>WhatsApp</li>
</ul>
</div>
)
}
ReactDOM.render(
<Welcome name="jack" />,
document.getElementById('app')
)
class创建
在es6中class仅仅是一个语法糖,不是真正的类,本质上还是构造函数+原型 实现继承
// ES6中class关键字的简单使用
// - **ES6中的所有的代码都是运行在严格模式中的**
// - 1 它是用来定义类的,是ES6中实现面向对象编程的新方式
// - 2 使用`static`关键字定义静态属性
// - 3 使用`constructor`构造函数,创建实例属性
// - [参考](http://es6.ruanyifeng.com/#docs/class)
// 语法:
class Person {
// 实例的构造函数 constructor
constructor(age){
// 实例属性
this.age = age
}
// 在class中定义方法 此处为实例方法 通过实例打点调用
sayHello () {
console.log('大家好,我今年' + this.age + '了');
}
// 静态方法 通过构造函数打点调用 Person.doudou()
static doudou () {
console.log('我是小明,我新get了一个技能,会暖床');
}
}
// 添加静态属性
Person.staticName = '静态属性'
// 实例化对象
const p = new Person(19)
// 实现继承的方式
class American extends Person {
constructor() {
// 必须调用super(), super表示父类的构造函数
super()
this.skin = 'white'
this.eyeColor = 'white'
}
}
// 创建react对象
// 注意:基于 `ES6` 中的class,需要配合 `babel` 将代码转化为浏览器识别的ES5语法
// 安装:`npm i -D babel-preset-env`
// react对象继承字React.Component
class ShoppingList extends React.Component {
constructor(props) {
super(props)
}
// class创建的组件中 必须有rander方法 且显示return一个react对象或者null
render() {
return (
<div className="shopping-list">
<h1>Shopping List for {this.props.name}</h1>
<ul>
<li>Instagram</li>
<li>WhatsApp</li>
</ul>
</div>
)
}
}
state vs props
state
是让组件控制自己的状态,props
是让外部对组件自己进行配置。
尽量少地用 state
,尽量多地用 props
。
列表渲染
const users = [
{ username: 'Jerry', age: 21, gender: 'male' },
{ username: 'Tony', age: 22, gender: 'male' },
{ username: 'Lily', age: 19, gender: 'female' },
{ username: 'Lucy', age: 20, gender: 'female' }
]
class User extends Component {
render () {
const { user } = this.props
return (
<div>
<div>姓名:{user.username}</div>
<div>年龄:{user.age}</div>
<div>性别:{user.gender}</div>
<hr />
</div>
)
}
}
class Index extends Component {
render () {
return (
// 没有 key 的话控制台会报错,但这种方法有些掩耳盗铃
<div>
{users.map((user, index) => <User key={index} user={user} />)}
</div>
)
}
}
prevState
组件生命周期
组件生命周期 - 创建阶段(Mounting)
- 特点:该阶段的函数只执行一次
constructor()
- 作用:1 获取 props; 2 初始化state
- 说明:通过
constructor()
的参数props
获取 - 设置state和props
class Greeting extends React.Component {
constructor(props) {
// 获取 props
super(props)
// 初始化 state
this.state = {
count: props.initCount
}
}
}
// 初始化 props
// 语法:通过静态属性 defaultProps 来初始化props
Greeting.defaultProps = {
initCount: 0
};
componentWillMount()
- 说明:组件被挂载到页面之前调用,其在render()之前被调用,因此在这方法里
同步
地设置状态将不会触发重渲染 - 注意:无法获取页面中的DOM对象
- 注意:可以调用
setState()
方法来改变状态值 - 用途:发送ajax请求获取数据
componentWillMount() {
console.warn(document.getElementById('btn')) // null
this.setState({
count: this.state.count + 1
})
}
render()
- 作用:渲染组件到页面中,无法获取页面中的DOM对象
- 注意:不要在render方法中调用
setState()
方法,否则会递归渲染- 原因说明:状态改变会重新调用
render()
,render()
又重新改变状态
- 原因说明:状态改变会重新调用
render() {
console.warn(document.getElementById('btn')) // null
return (
<div>
<button id="btn" onClick={this.handleAdd}>打豆豆一次</button>
{
this.state.count === 4
? null
: <CounterChild initCount={this.state.count}></CounterChild>
}
</div>
)
}
componentDidMount()
- 1 组件已经挂载到页面中
- 2 可以进行DOM操作,比如:获取到组件内部的DOM对象
- 3 可以发送请求获取数据
- 4 可以通过
setState()
修改状态的值 - 注意:在这里修改状态会重新渲染
componentDidMount() {
// 此时,就可以获取到组件内部的DOM对象
console.warn('componentDidMount', document.getElementById('btn'))
}
组件生命周期 - 运行阶段(Updating)
- 特点:该阶段的函数执行多次
- 说明:每当组件的
props
或者state
改变的时候,都会触发运行阶段的函数
componentWillReceiveProps()
- 说明:组件接受到新的
props
前触发这个方法 - 参数:当前组件
props
值 - 可以通过
this.props
获取到上一次的值 - 使用:若你需要响应属性的改变,可以通过对比
this.props
和nextProps
并在该方法中使用this.setState()
处理状态改变 - 注意:修改
state
不会触发该方法
componentWillReceiveProps(nextProps) {
console.warn('componentWillReceiveProps', nextProps)
}
shouldComponentUpdate()
- 作用:根据这个方法的返回值决定是否重新渲染组件,返回
true
重新渲染,否则不渲染 - 优势:通过某个条件渲染组件,降低组件渲染频率,提升组件性能
- 说明:如果返回值为
false
,那么,后续render()
方法不会被调用 - 注意:这个方法必须返回布尔值!!!
- 场景:根据随机数决定是否渲染组件
// - 参数:
// - 第一个参数:最新属性对象
// - 第二个参数:最新状态对象
shouldComponentUpdate(nextProps, nextState) {
console.warn('shouldComponentUpdate', nextProps, nextState)
return nextState.count % 2 === 0
}
componentWillUpdate()
- 作用:组件将要更新
- 参数:最新的属性和状态对象
- 注意:不能修改状态 否则会循环渲染
componentWillUpdate(nextProps, nextState) {
console.warn('componentWillUpdate', nextProps, nextState)
}
render() 渲染
- 作用:重新渲染组件,与
Mounting
阶段的render
是同一个函数 - 注意:这个函数能够执行多次,只要组件的属性或状态改变了,这个方法就会重新执行
componentDidUpdate()
- 作用:组件已经被更新
- 参数:旧的属性和状态对象
componentDidUpdate(prevProps, prevState) {
console.warn('componentDidUpdate', prevProps, prevState)
}
组件生命周期 - 卸载阶段(Unmounting)
- 组件销毁阶段:组件卸载期间,函数比较单一,只有一个函数,这个函数也有一个显著的特点:组件一辈子只能执行依次!
- 使用说明:只要组件不再被渲染到页面中,那么这个方法就会被调用( 渲染到页面中 -> 不再渲染到页面中 )
componentWillUnmount()
- 作用:在卸载组件的时候,执行清理工作,比如
- 1 清除定时器
- 2 清除
componentDidMount
创建的DOM对象
dangerouslySetInnerHTML
高阶组件
src/wrapWithLoadData.js
import React, { Component } from 'react'
export default (WrappedComponent, name) => {
class NewComponent extends Component {
constructor () {
super()
this.state = { data: null }
}
componentWillMount () {
let data = localStorage.getItem(name)
this.setState({ data })
}
render () {
return <WrappedComponent data={this.state.data} />
}
}
return NewComponent
}
src/InputWithUserName.js
import wrapWithLoadData from './wrapWithLoadData'
class InputWithUserName extends Component {
render () {
return <input value={this.props.data} />
}
}
InputWithUserName = wrapWithLoadData(InputWithUserName, 'username')
export default InputWithUserName
context (全局参数)
import React, { Component } from 'react'
class Index extends Component {
static childContextTypes = {
themeColor: PropTypes.string
}
constructor () {
super()
this.state = { themeColor: 'red' }
}
getChildContext () {
return { themeColor: this.state.themeColor }
}
render () {
return (
<div>
<Header />
<Main />
</div>
)
}
}
class Title extends Component {
static contextTypes = {
themeColor: PropTypes.string
}
render () {
return (
<h1 style={{ color: this.context.themeColor }}>React.js 小书标题</h1>
)
}
}
react-router 路由跳转
npm install -S react-router
npm i -S react-router-dom
(安装 react-router-dom 时会自动安装 react-router)
react-router v3 与 v4 的区别
import React from 'react'
import {Router, Route, BrowserRouter, Link} from 'react-router-dom'
import CommentApp from '../Comment/containers/CommentApp'
import Game from "../Game/Game";
import Clock from "../Clock/Clock";
const BaseLayout = () => (
<div className="base">
<header>
<nav>
<ul>
<li><Link to={"/game"}>Game</Link></li>
<li><Link to={"/clock"}>Clock</Link></li>
<li><Link to={"/comment/Samuel Liu"}>Comment</Link></li>
</ul>
</nav>
</header>
<div className="container">
<Route path="/game" exact component={Game}/>
<Route path="/clock" component={Clock}/>
{/* 传参 username */}
<Route path="/comment/:username" component={CommentApp}/>
</div>
</div>
);
const SubRouter = () => (
<BrowserRouter>
<BaseLayout/>
</BrowserRouter>
)
export default SubRouter
Redux
npm install redux react-redux –save
单一数据源
整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。
这让同构应用开发变得非常容易。来自服务端的 state 可以在无需编写更多代码的情况下被序列化并注入到客户端中。由于是单一的 state tree ,调试也变得非常容易。在开发中,你可以把应用的 state 保存在本地,从而加快开发速度。此外,受益于单一的 state tree ,以前难以实现的如“撤销/重做”这类功能也变得轻而易举。
console.log(store.getState())
/* 输出
{
visibilityFilter: 'SHOW_ALL',
todos: [
{
text: 'Consider using Redux',
completed: true,
},
{
text: 'Keep all state in a single tree',
completed: false
}
]
}
*/
State 是只读的
唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。
这样确保了视图和网络请求都不能直接修改 state,相反它们只能表达想要修改的意图。因为所有的修改都被集中化处理,且严格按照一个接一个的顺序执行,因此不用担心 race condition 的出现。 Action 就是普通对象而已,因此它们可以被日志打印、序列化、储存、后期调试或测试时回放出来。
store.dispatch({
type: 'COMPLETE_TODO',
index: 1
})
store.dispatch({
type: 'SET_VISIBILITY_FILTER',
filter: 'SHOW_COMPLETED'
})
使用纯函数来执行修改
为了描述 action 如何改变 state tree ,你需要编写 reducers。
Reducer 只是一些纯函数,它接收先前的 state 和 action,并返回新的 state。刚开始你可以只有一个 reducer,随着应用变大,你可以把它拆成多个小的 reducers,分别独立地操作 state tree 的不同部分,因为 reducer 只是函数,你可以控制它们被调用的顺序,传入附加数据,甚至编写可复用的 reducer 来处理一些通用任务,如分页器。
function visibilityFilter(state = 'SHOW_ALL', action) {
switch (action.type) {
case 'SET_VISIBILITY_FILTER':
return action.filter
default:
return state
}
}
function todos(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
{
text: action.text,
completed: false
}
]
case 'COMPLETE_TODO':
return state.map((todo, index) => {
if (index === action.index) {
return Object.assign({}, todo, {
completed: true
})
}
return todo
})
default:
return state
}
}
import { combineReducers, createStore } from 'redux'
let reducer = combineReducers({ visibilityFilter, todos })
let store = createStore(reducer)
react element-ui
npm install element-react –save
npm install element-theme-default –save
import {Button} from 'element-react';
import 'element-theme-default';
render() {
return (
<div>
<Button className="">kokoko</Button>
<Button>kokoko</Button>
</div>
)
}
react ant-design
import React, {Component} from "react";
import {Row, Col} from "antd";
import 'antd/dist/antd.css'
import {Button} from "@ant-design/icons/lib";
import TopBar from "./TopBar";
import LeftSideBar from "./LeftSideBar";
import Main from "./Main";
import './index.css'
export default class Home extends Component {
render() {
return (
<div>
<TopBar/>
<Row>
<Col span={4}><LeftSideBar/></Col>
<Col span={20}><Main/></Col>
</Row>
</div>
)
}
}
Tips
react-router url 更新但页面不更新
console Error:Uncaught Could not find router reducer in state tree, it must be mounted under “router”
- 由于history的最新版不兼容connect-react-router导致。
- history的最新版为5.0,而connect-react-router使用的history版本为4.7,从而导致reducer执行错误。
参考文章:https://blog.csdn.net/yehuozhili/article/details/107193780
react-router 和 react-router-dom 的区别
react-router
v4,我称之为“第四代 react-router”,react-router
和react-router-dom
的区别是什么呢?
为什么有时候我们看到如下的写法:
写法1:
import {Switch, Route, Router, HashHistory, Link} from 'react-router-dom';
写法2:
import {Switch, Route, Router} from 'react-router';
import {HashHistory, Link} from 'react-router-dom';
先简单说下各自的功能:
react-router
: 实现了路由的核心功能react-router-dom
: 基于react-router
,加入了在浏览器运行环境下的一些功能,例如:Link
组件,会渲染一个a
标签,Link组件源码a
标签行;BrowserRouter
和HashRouter
组件,前者使用pushState
和popState
事件构建路由,后者使用window.location.hash
和hashchange
事件构建路由。react-router-native
: 基于react-router
,类似react-router-dom
,加入了react-native
运行环境下的一些功能。
从源码层面来说明:
首先看react-router-dom
中的Switch
组件的源码
"use strict";
require("./warnAboutDeprecatedCJSRequire")("Switch");
module.exports = require("./index.js").Switch;
只是从react-router
中导入Switch
组件,然后重新导出而已。
查看其他模块的源码,Route
组件的源码、Router
组件的源码…和Switch
一样,都是从react-router
中导入了相应的组件,重新导出而已,并没有对实现做什么特殊处理。
结论:
react-router-dom
依赖react-router
,所以我们使用npm
安装依赖的时候,只需要安装相应环境下的库即可,不用再显式安装react-router
。基于浏览器环境的开发,只需要安装react-router-dom
;基于react-native
环境的开发,只需要安装react-router-native
。npm
会自动解析react-router-dom
包中package.json
的依赖并安装。react-router-dom
中package.json
依赖:"dependencies": { "history": "^4.7.2", "invariant": "^2.2.2", "loose-envify": "^1.3.1", "prop-types": "^15.5.4", "react-router": "^4.2.0", "warning": "^3.0.0" }
安装了
react-router-dom
,npm
会解析并安装上述依赖包。可以看到,其中包括react-router
。回到最开始的写法。基于浏览器环境的开发,写法1就可以了。