目录
一、React简介
React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写一套,用来架设 Instagram 的网站(https://www.instagram.com/)。做出来以后,发现这套东西很好用,就在2013年5月开源了(https://github.com/facebook/react)。由于 React 的设计思想极其独特,属于革命性创新,性能出众,代码逻辑却非常简单。所以,越来越多的人开始关注和使用,认为它可能是将来 Web 开发的主流工具。
官方网站:https://react.docschina.org/
英文官方网站:https://reactjs.org/
推荐书籍:https://blog.csdn.net/xutongbao/article/details/88638078
菜鸟教程:https://www.runoob.com/react/react-tutorial.html
NPM下载量对比:https://npmcharts.com/compare/react,vue?interval=30
instagram:
react github:
二、React的背景和原理
在Web开发中,我们总需要将变化的数据实时反应到UI上,这时就需要对DOM进行操作。而复杂或频繁的DOM操作通常是性能瓶颈产生的原因(如何进行高性能的复杂DOM操作通常是衡量一个前端开发人员技能的重要指标)。
React为此引入了虚拟DOM(Virtual DOM)的机制:在浏览器端用Javascript实现了一套DOM API。
Virtual DOM 本质上就是在 JS 和 DOM 之间做了一个缓存。可以类比 CPU 和硬盘,既然硬盘这么慢,我们就在它们之间加个缓存:既然 DOM 这么慢,我们就在它们 JS 和 DOM 之间加个缓存。CPU(JS)只操作内存(Virtual DOM),最后的时候再把变更写入硬盘(DOM)。
React在Virtual DOM上实现了DOM diff算法,当数据更新时,会通过diff算法计算出相应的更新策略,尽量只对变化的部分进行实际的浏览器的DOM更新,而不是直接重新渲染整个DOM树,从而达到提高性能的目的。
你给我一个数据,我根据这个数据生成一个全新的Virtual DOM,然后跟我上一次生成的Virtual DOM去 diff,得到一个Patch,然后把这个Patch打到浏览器的DOM上去。
你不要一步一步告诉我这件事情怎么做,什么先和面再剁馅,NO,告诉我你想要煎饼还是月饼,我会想办法去做的,不要来干扰我。
哪怕是我生成了virtual dom,哪怕是我跑了diff,但是我根据patch简化了那些DOM操作省下来的时间依然很可观。所以总体上来说,还是比较快。
用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异,把差异应用到真正的DOM树上,视图就更新了。
三、JSX简介
HTML 语言直接写在 JavaScript 语言之中,这就是 JSX(JavaScript and XML) 的语法。JSX,是一种 JavaScript 的语法扩展,它允许 HTML 与 JavaScript 的混写。JSX是facebook为React框架开发的一套语法糖,语法糖又叫做糖衣语法,是指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用,它主要的目的是增加程序的可读性,从而减少程序代码错处的机会。JSX就是JS的一种语法糖,类似的还有CoffeeScript、TypeScript,最终它们都会被解析成JS才能被浏览器理解和执行,如果不解析浏览器是没有办法识别它们的,这也是所有语法糖略有不足的地方。
const element = <h1>Hello, world!</h1>;
上面这种看起来可能有些奇怪的标签语法既不是字符串也不是HTML,被称为 JSX,JSX带来的一大便利就是我们可以直接在JS里面写类DOM的结构,比我们用原生的JS去拼接字符串,然后再用正则替换等方式来渲染模板方便和简单太多了。推荐在 React 中使用 JSX 来描述用户界面。JSX 用来声明 React 当中的元素, 乍看起来可能比较像是模版语言,但事实上它完全是在 JavaScript 内部实现的。
你可以任意地在 JSX 当中使用 JavaScript 表达式,在 JSX 当中的表达式要包含在大括号里。例子如下:
const names = ['Jack', 'Tom', 'Alice'];
const element = (
<div>
{ names.map(function (name) { return <div>Hello, {name}!</div>}) }
</div>
);
在书写 JSX 的时候一般都会带上换行和缩进,这样可以增强代码的可读性。与此同时,推荐在 JSX 代码的外面扩上一个小括号,这样可以防止分号自动插入的bug。
上面我们声明了一个names数组,然后遍历names数组在前面加上Hello,生成了element数组。JSX 允许直接在模板插入 JavaScript 变量。如果这个变量是一个数组,则会展开这个数组的所有成员。JSX 本身其实也是一种表达式,在编译之后,JSX 其实会被转化为普通的 JavaScript 对象。代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello World</title>
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const names = ['Jack', 'Tom', 'Alice'];
const element = names.map(function (name, index) { return <div key={index}>Hello, {name}!</div>});
ReactDOM.render(
element,
document.getElementById('root')
)
</script>
</body>
</html>
JSX属性:
你可以使用引号来定义以字符串为值的属性:
const element = <div tabIndex="0"></div>;
也可以使用大括号来定义以 JavaScript 表达式为值的属性:
const element = <img src={user.avatarUrl}></img>;
切记当使用了大括号包裹的 JavaScript 表达式时就不要再到外面套引号了。JSX 会将引号当中的内容识别为字符串而不是表达式
四、React组件
组件作为React的核心内容,是View的重要组成部分,每一个View页面都由一个或多个组件构成,可以说组件是React应用程序的基石。在React的组件构成中,按照状态来分可以分为有状态组件和无状态组件。
所谓无状态组件,就是没有状态控制的组件,只做纯静态展示的作用,无状态组件是最基本的组件形式,它由属性props和渲染函数render构成。由于不涉及到状态的更新,所以这种组件的复用性也最强。
有状态组件是在无状态组件的基础上增加了组件内部状态管理,有状态组件通常会带有生命周期lifecycle,用以在不同的时刻触发状态的更新,有状态组件被大量用在业务逻辑开发中。
接下来我们封装一个输出 "Hello World!" 的组件,组件名为 HelloMessage:
function HelloMessage(props) {
return <h1>Hello World!</h1>;
}
const element = <HelloMessage />;
ReactDOM.render(
element,
document.getElementById('example')
);
实例解析:
1、我们可以使用函数定义了一个组件:
function HelloMessage(props) {
return <h1>Hello World!</h1>;
}
也可以使用 ES6 class 来定义一个组件:
class HelloMessage extends React.Component {
render() {
return <h1>Hello World!</h1>;
}
}
2、const element = <HelloMessage /> 为用户自定义的组件。
注意,原生 HTML 元素名以小写字母开头,而自定义的 React 类名以大写字母开头,比如 HelloMessage 不能写成 helloMessage。除此之外还需要注意组件类只能包含一个顶层标签,否则也会报错。
如果我们需要向组件传递参数,可以使用 this.props 对象,实例如下:
function HelloMessage(props) {
return <h1>Hello {props.name}!</h1>;
}
const element = <HelloMessage name="Runoob"/>;
ReactDOM.render(
element,
document.getElementById('example')
);
注意,在添加属性时, class 属性需要写成 className ,for 属性需要写成 htmlFor ,这是因为 class 和 for 是 JavaScript 的保留字。
复合组件:
我们可以通过创建多个组件来合成一个组件,即把组件的不同功能点进行分离。
以下实例我们实现了输出网站名字和网址的组件:
function Name(props) {
return <h1>网站名称:{props.name}</h1>;
}
function Url(props) {
return <h1>网站地址:{props.url}</h1>;
}
function Nickname(props) {
return <h1>网站小名:{props.nickname}</h1>;
}
function App() {
return (
<div>
<Name name="菜鸟教程" />
<Url url="http://www.runoob.com" />
<Nickname nickname="Runoob" />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('example')
);
五、脚手架
npx create-react-app my-app
cd my-app
npm start
参考链接:https://facebook.github.io/create-react-app/docs/getting-started
在开发react应用时,应该没有人用传统的方法引入react的源文件(js),然后在html编辑吧。
大家都是用webpack + es6来结合react开发前端应用。
这个时候,我们可以手动使用npm来安装各种插件,来从头到尾自己搭建环境。
虽然自己搭建的过程也是一个很好的学习过程,但是有时候难免遇到各种问题,特别是初学者,而且每次开发一个新应用,都要自己从头搭建,未免太繁琐。
于是,有人根据自己的经验和最佳实践,开发了脚手架,避免开发过程中的重复造轮子和做无用功,从而节省开发时间。
create-react-app:https://github.com/facebook/create-react-app
六、快捷方式
VSC 安装 ES7 React/Redux/GraphQL/React-Native snippets
rcc:
import React, { Component } from 'react'
export default class App extends Component {
render() {
return (
<div>
</div>
)
}
}
rcreudx:
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
export class App extends Component {
static propTypes = {
prop: PropTypes
}
render() {
return (
<div>
</div>
)
}
}
const mapStateToProps = (state) => ({
})
const mapDispatchToProps = {
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
imrc:
import React, { Component } from 'react'
七、state和props
1、区别
props 是组件对外的接口,state 是组件对内的接口。组件内可以引用其他组件,组件之间的引用形成了一个树状结构(组件树),如果下层组件需要使用上层组件的数据或方法,上层组件就可以通过下层组件的props属性进行传递,因此props是组件对外的接口。组件除了使用上层组件传递的数据外,自身也可能需要维护管理数据,这就是组件对内的接口state。根据对外接口props 和对内接口state,组件计算出对应界面的UI。
主要区别:
- State是可变的,是一组用于反映组件UI变化的状态集合;
- 而Props对于使用它的组件来说,是只读的,要想修改Props,只能通过该组件的父组件修改。在组件状态上移的场景中,父组件正是通过子组件的Props, 传递给子组件其所需要的状态。
React 的核心思想是组件化,而组件中最重要的概念是State(状态),State是一个组件的UI数据模型,是组件渲染时的数据依据。
状态(state) 和 属性(props) 类似,都是一个组件所需要的一些数据集合,但是state是私有的,可以认为state是组件的“私有属性(或者是局部属性)”。
2、用setState 修改State
直接修改state,组件并不会重新触发render()
// 错误
this.state.comment = 'Hello';
正确的修改方式是使用setState()
// 正确
this.setState({comment: 'Hello'});
3、State 的更新是异步的
综上所述:
this.props 和 this.state 可能是异步更新的,你不能依赖他们的值计算下一个state(状态)
- 调用setState后,setState会把要修改的状态放入一个队列中(因而 组件的state并不会立即改变);
- 之后React 会优化真正的执行时机,来优化性能,所以优化过程中有可能会将多个 setState 的状态修改合并为一次状态修改,因而state更新可能是异步的。
- 所以不要依赖当前的State,计算下个State。当真正执行状态修改时,依赖的this.state并不能保证是最新的State,因为React会把多次State的修改合并成一次,这时,this.state将还是这几次State修改前的State。另外需要注意的事,同样不能依赖当前的Props计算下个状态,因为Props一般也是从父组件的State中获取,依然无法确定在组件状态更新时的值。
八、组件
父子组件传值,子组件调用父组件的方法
Control.js:
import React, {Component} from 'react'
class Control extends Component {
constructor(props) {
super(props)
}
handleAdd() {
this.props.onAdd()
}
render() {
return (
<div>
<button onClick={this.handleAdd.bind(this)}>加</button>
</div>
)
}
}
export default Control
Index.js:
import React, {Component} from 'react'
import Control from '../components/Control.js'
class Index extends Component {
constructor(props) {
super(props)
this.state = {
count: 0
}
}
handleAdd() {
let {count} = this.state
count = count + 1
this.setState({
count
})
}
render() {
let {count} = this.state
return (
<div>
<div>{count}</div>
<div>
<Control onAdd={this.handleAdd.bind(this)}/>
</div>
</div>
)
}
}
export default Index
九、todo list小练习
一个元素的 key 最好是这个元素在列表中拥有的一个独一无二的字符串。通常,我们使用来自数据 id 来作为元素的 key:
const todoItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
);
github地址:
动画版todolist:
http://chenglou.github.io/react-motion/demos/demo3-todomvc-list-transition/
十、生命周期
常用的:
包括不常用的:
React v16.0前的生命周期:
参考链接:http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/
文字叙述:https://zh-hans.reactjs.org/docs/react-component.html
实例期: componentWillMount render componentDidMount
存在期: componentWillReceiveProps shouldComponentUpdate componentWillUpdate render componentDidUpdate
销毁期: componentWillUnmount
最新推出的生命周期:
getDerivedStateFromProps:
父组件:
import React, { Component } from 'react'
import Count from '../components/Count.js'
class Index extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
}
}
handleAdd() {
let {count} = this.state
count = count + 1
this.setState({
count
})
}
componentDidMount() {
}
render() {
let {count} = this.state
return (
<div>
<Count count={count}></Count>
<button onClick={this.handleAdd.bind(this)}>加</button>
</div>)
}
}
export default Index
子组件:
import React, {Component} from 'react'
class Count extends Component {
constructor(props) {
super(props)
this.state = {
count: 0
}
}
static getDerivedStateFromProps(props, state) {
return {
count: props.count
}
}
render() {
let { count } = this.state
return (
<div>
{count}
</div>
)
}
}
export default Count
getSnapshotBeforeUpdate
父组件:
import React, { Component } from 'react'
import ScrollingList from '../components/ScrollingList.js'
class Index extends Component {
constructor(props) {
super(props);
this.state = {
list:[]
}
}
handleAdd() {
let {list} = this.state
list.unshift({
id: (new Date()).getTime(),
text: 'xu' + (new Date()).getTime(),
})
this.setState({
list
})
}
render() {
let {
list
} = this.state
return (
<div>
<button onClick={this.handleAdd.bind(this)}>加</button>
<ScrollingList list={list}></ScrollingList>
</div>
);
}
}
export default Index
子组件:
import React, { Component } from 'react'
import './index.css'
class ScrollingList extends Component {
constructor(props) {
super(props);
this.listRef = React.createRef();
}
getSnapshotBeforeUpdate(prevProps, prevState) {
// 我们是否在 list 中添加新的 items ?
// 捕获滚动位置以便我们稍后调整滚动位置。
if (prevProps.list.length < this.props.list.length || true) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
// 如果我们 snapshot 有值,说明我们刚刚添加了新的 items,
// 调整滚动位置使得这些新 items 不会将旧的 items 推出视图。
// (这里的 snapshot 是 getSnapshotBeforeUpdate 的返回值)
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
//list.scrollTop = snapshot
}
}
render() {
let {
list
} = this.props
return (
<div>
<ul className="m-list-wrap" ref={this.listRef}>
{
list.map(item => (
<li key={item.id}>
{item.text}
</li>
))
}
</ul>
</div>
);
}
}
export default ScrollingList
十一、路由
参考链接:https://reacttraining.com/react-router/web/api/BrowserRouter
常用api有:
BrowserRouter:包裹根组件
Switch: 有<Switch>标签,则其中的<Route>在路径相同的情况下,只匹配第一个,这个可以避免重复匹配
Route:路由
Link :链接
withRouter:包裹组件,包裹后组件的props会增加三个属性: match, location, history
1)通过js跳转路由:this.props.history.push('/tasklist')
2)获取动态路由参数
let { match } = this.props
if (match.params.new === 'new') {
}
3)获取路径名:<div>{this.props.location.pathname}</div>
首先加上了Switch,其次加入了exact。加入Switch表示,只显示一个组件。加exact表示精确匹配/
嵌套路由,从广义上来说,分为两种情况:一种是每个路由到的组件都有共有的内容,这时把共有的内容抽离成一个组件,变化的内容也是一个组件,两种组件组合嵌套,形成一个新的组件。另一种是子路由,路由到的组件内部还有路由。
对于共有的内容,典型的代表就是网页的侧边栏,假设侧边栏在左边,我们点击其中的按钮时,右侧的内容会变化,但不管右侧的内容怎么变化,左侧的侧边栏始终存在。
BrowserRouter 和 HashRouter 都可以实现前端路由的功能,区别是前者基于url的pathname段,后者基于hash段。
前者:http://127.0.0.1:3000/article/num1
后者:http://127.0.0.1:3000/#/article/num1(不一定是这样,但#是少不了的)
这样的区别带来的直接问题就是当处于二级或多级路由状态时,刷新页面,前者会将当前路由发送到服务器(因为是pathname),而后者不会(因为是hash段)。
多级路由:
使用react-router-dom router.js:
import React, { Component } from 'react'
import { Switch, Route, Redirect } from 'react-router-dom'
import Login from '../pages/Login.js'
import Header from '../components/Header.js'
import Sidebar from '../components/Sidebar.js'
import CityNav from '../components/CityNav.js'
import CityNavForShanDong from '../components/CityNavForShanDong.js'
import DistrictNav from '../components/DistrictNav.js'
import ShanXi from '../pages/province/ShanXi.js'
import TangShan from '../pages/province/city/TangShan.js'
import QinHuangDao from '../pages/province/city/QinHuangDao.js'
import JiNan from '../pages/province/city/JiNan.js'
import QingDao from '../pages/province/city/QingDao.js'
import YanTai from '../pages/province/city/YanTai.js'
import ChangAn from '../pages/province/city/district/ChangAn.js'
import QiaoXi from '../pages/province/city/district/QiaoXi.js'
import XinHua from '../pages/province/city/district/XinHua.js'
import './index.css'
class Router extends Component {
render() {
return (
<div>
<Switch>
<Route path='/' exact={true} component={Login}></Route>
<Route path='/index'>
<Route>
<Header></Header>
<Sidebar></Sidebar>
</Route>
<Switch>
<Route path="/index/hebei">
<Route>
<CityNav></CityNav>
</Route>
<Switch>
<Route path="/index/hebei/shijiazhuang">
<Route>
<DistrictNav></DistrictNav>
</Route>
<Switch>
<Route path="/index/hebei/shijiazhuang/changan" component={ChangAn}></Route>
<Route path="/index/hebei/shijiazhuang/qiaoxi" component={QiaoXi}></Route>
<Route path="/index/hebei/shijiazhuang/xinhua" component={XinHua}></Route>
</Switch>
</Route>
<Route path="/index/hebei/tangshan" component={TangShan}></Route>
<Route path="/index/hebei/qinhuangdao" component={QinHuangDao}></Route>
</Switch>
</Route>
<Route path="/index/shandong">
<Route>
<CityNavForShanDong></CityNavForShanDong>
</Route>
<Route path="/index/shandong/jinan" component={JiNan}></Route>
<Route path="/index/shandong/qingdao" component={QingDao}></Route>
<Route path="/index/shandong/yantai" component={YanTai}></Route>
</Route>
<Route path="/index/shanxi" component={ShanXi}></Route>
</Switch>
</Route>
</Switch>
</div>
)
}
}
export default Router
github源码(使用react-router-dom):
使用react-router-config routerConfig.js:
import React from 'react';
import { renderRoutes } from "react-router-config";
import Login from '../pages/Login.js'
import Header from '../components/Header.js'
import Sidebar from '../components/Sidebar.js'
import CityNav from '../components/CityNav.js'
import CityNavForShanDong from '../components/CityNavForShanDong.js'
import DistrictNav from '../components/DistrictNav.js'
import ShanXi from '../pages/province/ShanXi.js'
import TangShan from '../pages/province/city/TangShan.js'
import QinHuangDao from '../pages/province/city/QinHuangDao.js'
import JiNan from '../pages/province/city/JiNan.js'
import QingDao from '../pages/province/city/QingDao.js'
import YanTai from '../pages/province/city/YanTai.js'
import ChangAn from '../pages/province/city/district/ChangAn.js'
import QiaoXi from '../pages/province/city/district/QiaoXi.js'
import XinHua from '../pages/province/city/district/XinHua.js'
const Root = ({ route }) => (
<div>
{renderRoutes(route.routes)}
</div>
);
const Index = ({ route }) => (
<div>
<Header></Header>
<Sidebar></Sidebar>
{renderRoutes(route.routes)}
</div>
);
const HeBei = ({ route }) => (
<div>
<CityNav></CityNav>
{renderRoutes(route.routes)}
</div>
);
const ShiJiaZhuang = ({ route }) => (
<div>
<DistrictNav></DistrictNav>
{renderRoutes(route.routes)}
</div>
);
const ShanDong = ({ route }) => (
<div>
<CityNavForShanDong></CityNavForShanDong>
{renderRoutes(route.routes)}
</div>
);
const routes = [
{
component: Root,
routes: [
{
path: "/",
exact: true,
component: Login
},
{
path: "/index",
component: Index,
routes: [
{
path: "/index/hebei",
component: HeBei,
routes: [
{
path: "/index/hebei/shijiazhuang",
component: ShiJiaZhuang,
routes: [
{
path: '/index/hebei/shijiazhuang/changan',
component: ChangAn,
},
{
path: '/index/hebei/shijiazhuang/qiaoxi',
component: QiaoXi,
},
{
path: '/index/hebei/shijiazhuang/xinhua',
component: XinHua,
}
]
},
{
path: "/index/hebei/tangshan",
component: TangShan
},
{
path: "/index/hebei/qinhuangdao",
component: QinHuangDao,
}
]
},
{
path: "/index/shandong",
component: ShanDong,
routes: [
{
path: "/index/shandong/jinan",
component: JiNan
},
{
path: "/index/shandong/qingdao",
component: QingDao
},
{
path: "/index/shandong/yantai",
component: YanTai
}
]
},
{
path: "/index/shanxi",
component: ShanXi
}
]
}
]
}
];
export default routes
github地址(使用react-router-coinfg):
拆分后更好维护:
import React, { Component } from 'react'
import { Switch, Route, Redirect } from 'react-router-dom'
import Login from '../pages/Login'
import ShanDong from '../pages/ShanDong'
import ShanXi from '../pages/ShanXi'
import ShiJiaZhuang from '../pages/ShiJiaZhuang'
import ZhangJiaKou from '../pages/ZhangJiaKou'
import ChengDe from '../pages/ChengDe'
import NoFound from '../pages/NoFound'
import Header from '../components/Header'
import Sidebar from '../components/Sidebar'
import HeBeiCity from '../components/HeBeiCity'
import './index.css'
const HeBei = () => {
return (
<>
<Route>
<HeBeiCity></HeBeiCity>
</Route>
<Route path="/index/hebei/shijiazhuang" component={ShiJiaZhuang}></Route>
<Route path="/index/hebei/zhangjiakou" component={ZhangJiaKou}></Route>
<Route path="/index/hebei/chengde" component={ChengDe}></Route>
</>
)
}
const Index = () => {
return (
<>
<Route>
<Header></Header>
<Sidebar></Sidebar>
</Route>
<Switch>
<Route path="/index/hebei" component={HeBei}></Route>
<Route path="/index/shandong" component={ShanDong}></Route>
<Route path="/index/shanxi" component={ShanXi}></Route>
</Switch>
</>
)
}
export default class Router extends Component {
render() {
return (
<>
<Switch>
<Redirect exact from="/" to="/login"></Redirect>
<Redirect exact from="/index/hebei" to="/index/hebei/shijiazhuang"></Redirect>
<Route path="/login" component={Login}></Route>
<Route path="/index" component={Index}></Route>
<Route component={NoFound}></Route>
</Switch>
</>
)
}
}
github源代码:
十二、使用 PropTypes 进行类型检查
参考链接:https://zh-hans.reactjs.org/docs/typechecking-with-proptypes.html#___gatsby
十三、refs 和DOM
参考链接:https://zh-hans.reactjs.org/docs/refs-and-the-dom.html#___gatsby
ref添加到Component上获取的是Component实例,添加到原生HTML上获取的是DOM
第一种用法(createRef):
import React, { Component } from 'react'
class MyComponent extends Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
handleFocus() {
this.myRef.current.focus()
}
componentDidMount() {
this.myRef.current.focus()
}
render() {
return (
<div>
<input ref={this.myRef} />
<button onClick={this.handleFocus.bind(this)}>获取焦点</button>
</div>)
}
}
export default MyComponent
第二种写法(回调函数):
import React, { Component } from 'react'
class MyComponent extends Component {
constructor(props) {
super(props);
}
handleFocus() {
this.input.focus()
}
setInputRef(element) {
this.input = element
}
componentDidMount() {
this.input.focus()
}
render() {
return (
<div>
<input ref={this.setInputRef.bind(this)} />
<button onClick={this.handleFocus.bind(this)}>获取焦点</button>
</div>)
}
}
export default MyComponent
第三种写法(字符串,过时 API,不要再使用):
import React, { Component } from 'react'
class MyComponent extends Component {
constructor(props) {
super(props);
}
handleFocus() {
this.refs.input.focus()
}
componentDidMount() {
this.refs.input.focus()
}
render() {
return (
<div>
<input ref='input' />
<button onClick={this.handleFocus.bind(this)}>获取焦点</button>
</div>)
}
}
export default MyComponent
第四种写法(子组件传递ref给父组件):
父组件:
import React, { Component } from 'react'
import Input from '../components/Input.js'
class Index extends Component {
constructor(props) {
super(props);
}
handleFocus() {
this.input.focus()
}
handleSetInputRef(element) {
this.input = element
}
componentDidMount() {
this.input.focus()
}
render() {
return (
<div>
<Input onInputRef={this.handleSetInputRef.bind(this)}></Input>
<button onClick={this.handleFocus.bind(this)}>获取焦点</button>
</div>)
}
}
export default Index
子组件:
import React, {Component} from 'react'
class Input extends Component {
render() {
return (
<div>
<input ref={this.props.onInputRef.bind(this)}></input>
</div>
)
}
}
export default Input
十四、ReactDOM
findDOMNode是ReactDOM中的方法
ReactDOM.findDOMNode(this)返回的是组件实例相对应的DOM节点
import React, { Component } from 'react'
import ReactDOM from 'react-dom';
class Index extends Component {
constructor(props) {
super(props);
}
handleClick() {
let dom = ReactDOM.findDOMNode(this)
console.log(dom)
}
componentDidMount() {
}
render() {
return (
<div>
<button onClick={this.handleClick.bind(this)}>按钮</button>
</div>)
}
}
export default Index
findDOMNode当参数是DOM,返回值就是该DOM:
import React, { Component } from 'react'
import ReactDOM from 'react-dom';
class Index extends Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
handleClick() {
let dom = ReactDOM.findDOMNode(this.myRef.current)
console.log(dom)
}
componentDidMount() {
}
render() {
return (
<div>
<input ref={this.myRef} />
<button onClick={this.handleClick.bind(this)}>按钮</button>
</div>)
}
}
export default Index
find
十五、Mock数据
参考链接:https://github.com/nuysoft/Mock/wiki/Getting-Started
十六、antd
参考链接:https://ant.design/docs/react/introduce-cn
十七、Context
参考链接:https://zh-hans.reactjs.org/docs/context.html#___gatsby
index.js:
import React, {Component} from 'react'
import {ThemeContext} from './theme-context';
import Toolbar from '../components/Toolbar.js'
class Index extends React.Component {
constructor(props) {
super(props)
this.state = {
count: 0
}
}
handleAdd() {
let {count} = this.state
count = count + 1
this.setState({
count
})
}
render() {
let {count} = this.state
return (
<div>
<ThemeContext.Provider value={count}>
<Toolbar/>
<button onClick={this.handleAdd.bind(this)}>加</button>
</ThemeContext.Provider>
</div>
);
}
}
export default Index
theme-context.js:
import React from 'react'
export const ThemeContext = React.createContext('light');
Tootbar.js:
import React, {Component} from 'react'
import ThemedButton from './ThemedButton.js'
class Toolbar extends Component {
render() {
return (
<ThemedButton/>
)
}
}
export default Toolbar
ThemedButton.js:
import React, {Component} from 'react'
import {ThemeContext} from '../pages/theme-context';
class ThemedButton extends Component {
render() {
let value = this.context
return (
<div>{value}</div>
)
}
}
ThemedButton.contextType = ThemeContext;
export default ThemedButton
十八、事件系统
Virtual DOM在内存中是以对象的形式存在,如果想要在这些对象上加事件就会比较简单。React基于Virtual DOM实现了一个合成事件层,我们所定义的事件会接受到一个合成事件对象的实例。不会存在IE浏览器兼容性的问题,同样支持事件冒泡机制。
React事件的绑定方式在写法上与原生HTML事件监听很相似:
<button onClick={this.handleClick.bind(this)}></button>
这个和JavaScript的DOM0级事件很像,但是又有一些不同,下面是DOM0级事件:
<button οnclick="handle()"></button>
React并不会像DOM0级事件那样将事件处理器直接绑定到DOM上,React仅仅是借鉴了这种写法。
在React底层,主要对合成事件做了两件事情:事件委派和自动绑定。
1. 事件委派
React中并不是把事件处理函数绑定到当前DOM上,而是把所有的事件绑定到结构的最外层,使用统一的事件监听器。
这个事件监听器上维持了一个映射来保存所有组件内部的事件监听和处理函数。
组件挂载和卸载时,只是在统一事件监听器上插入删除一些对象。
2. 自动绑定
在React组件中,每个方法的上下文都会指向该组件的实例,即自动绑定this为当前的组件。而且React会对这种引用缓存,以达到CPU和内存的最大优化。
注意:在使用es6 class或者纯函数的写法,这种绑定不复存在,我们需要手动实现this绑定。
bind方法:该方法可以帮助我们绑定事件处理器内的this,并可以向事件处理器中传入参数:
class App extends Component {
handleClick(e, arg) {
console.log(e, arg)
}
render() {
return <button onClick={this.handleClick.bind(this, 'test')}></button>
}
}
十九、Redux
解决的问题:
原理:
动画:
二十、redux-thunk
源代码:
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
编译成es5:
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
function createThunkMiddleware(extraArgument) {
return function (_ref) {
var dispatch = _ref.dispatch,
getState = _ref.getState;
return function (next) {
return function (action) {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
};
};
}
var thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
var _default = thunk;
exports.default = _default;
Redux的核心概念其实很简单:将需要修改的state都存入到store里,发起一个action用来描述发生了什么,用reducers描述action如何改变state tree 。创建store的时候需要传入reducer,真正能改变store中数据的是store.dispatch API。
dispatch一个action之后,到达reducer之前,进行一些额外的操作,就需要用到middleware。你可以利用 Redux middleware 来进行日志记录、创建崩溃报告、调用异步接口或者路由等等。换言之,中间件都是对store.dispatch()的增强。
中间件的用法:
import { createStore, combineReducers, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import reducers from './reducers'
const reducer = combineReducers(reducers)
const store = createStore(reducer, applyMiddleware(thunk))
export default store
直接将thunk中间件引入,放在applyMiddleware方法之中,传入createStore方法,就完成了store.dispatch()的功能增强。即可以在reducer中进行一些异步的操作。
其实applyMiddleware就是Redux的一个原生方法,将所有中间件组成一个数组,依次执行。
手写中间件(logger,thunk) :
import { createStore, combineReducers, applyMiddleware } from 'redux'
import reducers from './reducers'
const logger = store => next => action => {
console.log('prev state', store.getState())
console.log('dispatch', action)
next(action)
console.log('next state', store.getState())
}
const logger2 = store => next => action => {
console.log('prev state2', store.getState())
console.log('dispatch2', action)
next(action)
console.log('next state2', store.getState())
}
const myThunk = store => next => action => {
if (typeof action === 'function') {
return action(store.dispatch)
}
return next(action)
}
const reducer = combineReducers(reducers)
const store = createStore(reducer, applyMiddleware(myThunk, logger, logger2))
export default store
参考链接:https://segmentfault.com/a/1190000018347235
二十一、flex三段式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, width=device-width">
<title>Document</title>
<style type="text/css">
html {
height: 100%;
}
body {
margin: 0;
height: 100%;
}
.m-wrap {
display: flex;
flex-direction: column;
height: 100%;
}
.m-header {
height: 50px;
background: #aaaaaa;
}
.m-content {
flex: 1;
background: #dddddd;
}
.m-footer {
height: 50px;
background: #cccccc;
}
</style>
</head>
<body>
<div class="m-wrap">
<header class="m-header">head</header>
<main class="m-content">main</main>
<footer class="m-footer">footer</footer>
</div>
</body>
</body>
</html>
二十二、小米书城
github地址:
二十三、当当网
github地址:
https://github.com/baweireact/m-react-demo-base/tree/master/30%E5%BD%93%E5%BD%93%E7%BD%91
二十四、轮播图
github地址:
二十五、登录和组件嵌套
github地址:
二十六、猫眼
github地址:
https://github.com/baweireact/m-react-demo-base/tree/master/31%E7%8C%AB%E7%9C%BC
二十七、购物车
github地址:
其他
forceUpdate方法能使组件调用自身的render()方法重新渲染组件:
handleUpate() {
this.forceUpdate()
}
reactDom.unmountComponentAtNode()方法里接收指定的容器参数:
从 DOM 中卸载组件,会将其事件处理器(event handlers)和 state 一并清除。如果指定容器上没有对应已挂载的组件,这个函数什么也不会做。如果组件被移除将会返回 true
,如果没有组件可被移除将会返回 false
。
handleUnmount() {
ReactDOM.unmountComponentAtNode(document.getElementById('root'))
}
componentWillUnmount() {
console.log('卸载前')
}
组件里面绑定的合成事件想要通过 e.stopPropagation() 来阻止事件冒泡到 document如何解决
e.nativeEvent.stopImmediatePropagation()