概念 官方 是这样定义的:用于构建用户界面的 JavaScript 库 。
它有如下几个特点:
声明式编程;
组件化开发;
多平台适配;
开发依赖 我们使用 React 开发必须依赖这三个库:
react:包含所必须的核心代码;
react-dom:react 渲染在不同平台所需要的核心代码;
babel :将 jsx 转换成 react 代码的工具;
React 不是像 Vue 那样只需要依赖一个 vue.js 文件即可。这三个库都是各司其职,让每一个库都只做自己的事情。
react-dom 针对 web 和 native 所完成的事情是不同的:
web:会将 jsx 最终渲染成真实的 DOM 并显示在浏览器中;
native:会将 jsx 最终渲染成原生的控件;
我们有三种方式可以添加这些依赖:
CDN 引入;
下载并添加本地依赖;
脚手架;
下面我们暂时就使用 CDN 方式来引入:
1 2 3 4 <script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script> <script src="https://unpkg.com/babel-standalone@6/babel.min.js" ></script>
crossorigin
这个属性目的是拿到远程脚本的错误信息。
注意:
使用 jsx,并希望 script 中的 jsx 代码被解析,必须给 script 标签添加 type 属性:
1 <script type="text/babel" ></script>
在部署上线时,需要将 development.js
替换为 production.min.js
。
Hello World
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script> <script src="https://unpkg.com/babel-standalone@6/babel.min.js" ></script> <script type="text/babel" > let message = 'hello world' function btnClick ( ) { message = 'hello react' render() } function render ( ) { ReactDOM.render( <div> <h2>{message}</h2> <button onClick={btnClick}>改变内容</button> </div>, document .getElementById("app" ) ) } render() </script>
注意:ReactDOM.render()
的第一个参数必须只能有一个根节点。
还可以这样写更加明了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class App extends React .Component { constructor ( ) { super () this .state = { message: 'hello world' } } render ( ) { return ( <div> <h1>{this .state.message}</h1> <button onClick={this .btnClick.bind(this )}>改变文本</button> </div> ) } btnClick ( ) { this .setState({ message: 'hello react' }) } } ReactDOM.render(<App /> , document .getElementById('app' ))
下面我们就来开始正式的学习之旅了。
JSX语法 介绍
JSX 是一种 JavaScript 的语法扩展(eXtension),在很多地方被称为 JavaScript XML,因为看起来像是一段 XML 语法。
那我们来看一段 JSX
语法:
1 2 3 4 <script type="text/babel" > const element = <h1 > hello react</h1 > ReactDOM.render(element, document .getElementById('app' )) </script>
JSX 的书写规范如下:
JSX 顶层只能有一个根元素;
通常在 JSX 的外层包裹一个小括号();
JSX 的标签可以是单标签,也可也是双标签。如果是单标签,必须以 />
结尾;
语法 注释 在 JSX 中写注释的格式为:{/* 我是注释 */}
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class App extends React .Component { constructor (props ) { super (props) this .state = {} } render ( ) { return ( <div> {} { } <h2>hello</h2> <h3>react</h3> </div> ) } } ReactDOM.render(<App /> , document .getElementById('app' ))
嵌入变量 在 JSX 中嵌入变量有以下三种情况:
正常显示:变量类型为 Number、String、Array 时;
不正常显示:变量类型为 Null、Undefined、Boolean 时;
对象类型不能作为子元素展示;
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 class App extends React .Component { constructor (props ) { super (props) this .state = { name: "LqZww" , age: 18 , arr: [1 , 2 , 3 ], test1: null , test2: undefined , test3: false , test4: true , obj: { name: "LqZww" , age: 18 } } } render ( ) { return ( <div> {} <h2>{this .state.name}</h2> <h2>{this .state.age}</h2> <h2>{this .state.arr}</h2> {} <h2>{this .state.test1}</h2> <h2>{this .state.test2}</h2> <h2>{this .state.test3}</h2> <h2>{this .state.test4}</h2> {} <h2>{this .state.obj}</h2> {} <h2>{this .state.obj.name}</h2> <h2>{this .state.obj.age}</h2> </div> ) } } ReactDOM.render(<App /> , document .getElementById('app' ))
如果我们非要把 Null、Undefined、Boolean 类型展示出来可以使用如下方法:
Boolean 类型可以使用 toString()
方法;
使用 String()
方法:<h2>{String(this.state.test)}</h2>
;
使用空字符串拼接:<h2>{this.state.test + ""}</h2>
;
嵌入表达式 我们可以在 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 27 class App extends React .Component { constructor (props ) { super (props) this .state = { firstName: "Lq" , lastName: "Zww" , isShow: true } } render ( ) { const { firstName, lastName, isShow } = this .state return ( <div> <h2>{firstName + lastName}</h2> <h2>{10 + 20 }</h2> <h2>{isShow ? '显示' : "不显示" }</h2> <h2>{this .getMyName()}</h2> </div> ) } getMyName ( ) { return this .state.firstName + this .state.lastName } } ReactDOM.render(<App /> , document .getElementById('app' ))
绑定属性 下面我们就来简单的看看在 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 27 28 29 30 31 32 class App extends React .Component { constructor (props ) { super (props) this .state = { title: "标题" , imgUrl: "/imgurl" , active: true } } render ( ) { const { title, imgUrl, active } = this .state return ( <div> {} <h2 title={title}>标题</h2> <img src={imgUrl} /> {} <div className="box" >class </div > <div className ={"box " + (active ? "active" : "" )}>class </div > <label htmlFor ="" ></label> {} <div style={{ color : "red" , fontSize : "30px" }}>style</div> </div> ) } } ReactDOM.render(<App /> , document .getElementById('app' ))
事件绑定 基本使用 在原生 JavaScript 中,我们监听事件有如下两种方式:
获取 DOM,添加监听事件;
绑定 onClick;
在 JSX 中,也是使用 onClick
来进行事件绑定,但是对于 this
有以下几种情况:
使用 bind
来绑定 this
:
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 27 class App extends React .Component { constructor (props ) { super (props) this .state = { title: "hello" , } this .btnClick1 = this .btnClick1.bind(this ) } render ( ) { return ( <div> <button onClick={this .btnClick.bind(this )}>按钮</button> <button onClick={this .btnClick1}>按钮</button> </div> ) } btnClick ( ) { console .log(this .state.title); } btnClick1 ( ) { console .log(this .state.title); } } ReactDOM.render(<App /> , document .getElementById('app' ))
定义函数时,使用箭头函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class App extends React .Component { constructor (props ) { super (props) this .state = { count: 99 } } render ( ) { const { } = this .state return ( <div> <button onClick={this .addCount}>+1 </button> </div> ) } addCount = () => { console .log(this .state.count); } } ReactDOM.render(<App /> , document .getElementById('app' ))
直接传入一个箭头函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class App extends React .Component { constructor (props ) { super (props) this .state = { title: "hello" , count: 99 } } render ( ) { const { } = this .state return ( <div> <button onClick={() => { this .redCount() }}>-1 </button> </div> ) } redCount = () => { console .log(this .state.count); } } ReactDOM.render(<App /> , document .getElementById('app' ))
传参 下面就来演示一下事件绑定如何传参:
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 27 28 29 30 31 32 33 34 35 class App extends React .Component { constructor (props ) { super (props) this .state = { list: ['lq' , 'zww' , 'LqZww' ] } this .btnClick = this .btnClick.bind(this ) } render ( ) { return ( <div> {} <button onClick={this .btnClick}>+1 </button> {} <ul> { this .state.list.map((item, index, arr ) => { return <li onClick ={(e) => { this.liClick(item, index, e) }}>{item}</li > }) } </ul> </div> ) } btnClick (e ) { console .log(e); } liClick (item, index, e ) { console .log(item, index, e); } } ReactDOM.render(<App /> , document .getElementById('app' ))
条件渲染 在 Vue 中,一般我们会通过使用指令来进行控制,比如:v-if
、v-show
。
在 React 中所有的条件判断都与普通的 JavaScript 代码一致。
那么常见的条件渲染方式有哪几种呢:
条件判断语句;
三元运算符;
逻辑与 &&
;
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 class App extends React .Component { constructor (props ) { super (props) this .state = { isShow: true } } render ( ) { let show = null let text = null if (this .state.isShow) { show = <h2 > show</h2 > text = "happy" } else { show = <h2 > no show</h2 > text = "sad" } return ( <div> {show} <button>{text}</button> {} <button onClick={e => this .switchBtn()}>{this .state.isShow ? "happy" : "sad" }</button> {} <h2>{this .state.isShow && "hello React" }</h2> </div> ) } switchBtn ( ) { this .setState({ isShow: !this .state.isShow }) } } ReactDOM.render(<App /> , document .getElementById('app' ))
下面我们就来实现一下 Vue 中 v-show
的效果:
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 class App extends React .Component { constructor (props ) { super (props) this .state = { isShow: true } } render ( ) { const { isShow } = this .state; const changeDisplay = isShow ? 'block' : 'none' ; return ( <div> <h2 style={{ display : changeDisplay }}>hello React</h2> <button onClick={e => this .switchBtn()}>{isShow ? '隐藏' : '显示' }</button> </div> ) } switchBtn ( ) { this .setState({ isShow: !this .state.isShow }) } } ReactDOM.render(<App /> , document .getElementById('app' ))
列表渲染 在 React 中不像 Vue 那样使用 v-for
,而是需要使用到 map
来进行列表渲染。
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 27 28 29 30 31 32 33 34 35 36 37 class App extends React .Component { constructor (props ) { super (props) this .state = { list: [1 , 2 , 3 , 4 , 5 ], filterList: [321 , 3123 , 12 , 32 , 1 , 434 , 34 , 121 ] } } render ( ) { return ( <div> <ul> { this .state.list.map((item, index, arr ) => { return <li > {item} - {index}</li > }) } </ul> <hr /> <ul> { this .state.filterList.filter(item => { return item >= 200 }).map(item => { return <li > {item}</li > }) } </ul> </div> ) } } ReactDOM.render(<App /> , document .getElementById('app' ))
本质 实际上,JSX
只是 React.createElement(component,props,...children)
函数的语法糖。
所有的 JSX
最终都会被转换成 React.createElement
的函数调用。
1 2 3 4 const mes1 = <h2 > hello react</h2 > const mes2 = React.createElement("h2" , null , "hello react" )ReactDOM.render(mes1, document .getElementById('app' ))
当我们把 mes1
换成 mes2
后,会发现渲染的内容是一样的。
React.createElement(type,config,children)
需要传递三个参数:
type:当前 ReactElement 的类型,如果是标签就使用字符串来表示(”div”),如果是组件就直接使用组件名称;
config:所有 JSX 中的属性都在 config 中以对象的属性和值的形式存储;
children:存放标签中的内容,以数组的方式存储;
我们可以去 babeljs.io 中将 JSX 代码进行转换。
…
脚手架 安装 在安装 React 脚手架之前,我们必须先安装 node 。
安装 node 后会默认安装好 npm 包管理工具,除了它还有大名鼎鼎的 yarn。
React 脚手架默认也是使用的 yarn 。
下面我们就可以来安装脚手架了:
1 npm install -g create-react-app
使用如下命令检查是否安装成功:
1 create-react-app --version
创建 下面我们就来创建一个 React 项目。
使用下面这个命令来创建:
注意:项目名称不能使用大写字母。
创建完成后进入目录,并运行:
目录结构 下面就来看一看 React 脚手架的目录结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ├── node_modules // 依赖 ├── public │ ├── favicon.ico // 图标 │ ├── index.html // 入口页面 │ ├── logo192.png // logo │ ├── logo512.png // logo │ ├── manifest.json // 与web app配置相关 │ └── robots.txt // 设置爬虫规则 ├── src │ ├── App.css // app 组件样式文件 │ ├── App.js // app 组件代码文件 │ ├── App.test.js // 测试用例 │ ├── index.css // 全局样式文件 │ ├── index.js // react 代码入口 │ ├── logo.svg // svg图 │ ├── reportWebVitals.js // 默认写好的注册 PWA 相关代码 │ └── setupTests.js // 做测试前的初始化文件 ├── .gitignore // git的忽略文件 ├── package.json // 项目配置的管理文件 ├── README.md // 项目的描述 └── yarn.lock // 记录真实的版本依赖
生命周期 常用生命周期函数 很多事物都有从创建到销毁的整个过程,这个过程被称为生命周期。
生命周期与生命周期函数的关系:
生命周期的整个过程分为很多个阶段:
装载阶段(Mount):组件第一次在 DOM 树中被渲染的过程;
更新阶段(Update):组件状态发生变化,重新更新渲染的过程;
卸载阶段(Unmount):组件从 DOM 树中移除的过程;
React 会对组件内部实现的某些函数进行回调,这些函数就是生命周期函数:
比如实现 componentDidMount 函数:组件已经挂载到 DOM 上就会回调;
比如实现 componentDidUpdate 函数:组件发生了更新就会回调;
比如实现 componentWillUnmount 函数:组件即将被移除就会回调;
我们来看看挂载阶段 的执行顺序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 export default class App extends Component { constructor ( ) { super () console .log("执行constructor" ); } render ( ) { console .log("执行render" ); return ( <div></div> ) } componentDidMount ( ) { console .log("执行componentDidMount" ); } }
挂载阶段的执行顺序为:执行constructor -> 执行render -> 执行componentDidMount
。
下面是更新阶段 的执行顺序:
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 27 28 29 30 31 32 33 export default class App extends Component { constructor ( ) { super () this .state = { count: 0 } console .log("执行constructor" ); } render ( ) { console .log("执行render" ); return ( <div> <button onClick={e => this .addBtn()}>+1 </button> <h2>{this .state.count}</h2> </div> ) } addBtn ( ) { this .setState({ count: this .state.count + 1 }) } componentDidMount ( ) { console .log("执行componentDidMount" ); } componentDidUpdate ( ) { console .log("执行componentDidUpdate" ); } }
当我们点击按钮时,它的执行顺序为:执行render -> 执行componentDidUpdate
最后我们再来看看卸载阶段 :
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 class Cpn extends Component { render ( ) { return <h2 > cpn组件</h2 > } componentWillUnmount ( ) { console .log("执行cpn的componentWillUnmount" ); } } export default class App extends Component { constructor ( ) { super () this .state = { count: 0 , isShow: true } console .log("执行constructor" ); } render ( ) { console .log("执行render" ); return ( <div> <button onClick={e => this .addBtn()}>+1 </button> <h2>{this .state.count}</h2> <hr /> {this .state.isShow && <Cpn /> } <button onClick={e => this .switchBtn()}>切换</button> </div> ) } switchBtn ( ) { this .setState({ isShow: !this .state.isShow }) } addBtn ( ) { this .setState({ count: this .state.count + 1 }) } componentDidMount ( ) { console .log("执行componentDidMount" ); } componentDidUpdate ( ) { console .log("执行componentDidUpdate" ); } }
当我们点击切换按钮 后,会发现执行顺序为:执行render -> 执行cpn的componentWillUnmount -> 执行componentDidUpdate
。
constructor
通常只做下面两件事:
给 this.state
赋值对象来初始化内部的 state;
为事件绑定实例;
componentDidMount
会在组件挂载后(插入DOM)立即调用,它通常在下面几点进行操作:
依赖于 DOM 的操作;
发送网络请求;
添加订阅(在 componentWillUnmount 取消订阅);
componentDidUpdate
会在更新后立即被调用,首次渲染不会执行:
当组件更新后,可以对 DOM 进行操作;
如果对更新前后的 props 进行了比较,也可以选择在此处进行网路请求;
componentWillUnmount
会在组件卸载及销毁前调用。可以在此方法执行必要的清理操作,例如:清除定时器、取消网络请求或清除在 componentDidMount() 中创建的订阅等。
不常用生命周期函数 React 中还有一些不常用的生命周期函数:
getDerivedStateFromProps:state 值在任何时候都依赖于 props 时使用,该方法返回一个对象来更新 state;
getSnapshotBeforeUpdate:在 React 更新 DOM 之前回调的函数,可以获取 DOM 更新前的一些信息;
shouldComponentUpdate:在重新渲染 render() 函数时调用前被调用的函数,在性能优化方面常用。
除这些外,还有一些过时的生命周期,官方已推荐不建议使用。
组件化 概念 在我们开发中,常常会使用到组件化思想 :
将一个完整的页面分成多个组件;
每个组件都用于实现页面的一个功能块;
每一个组件又可以进行细分;
组件本身可以在多个地方进行复用;
在 React 的组件相对于 Vue 中更加灵活和多样,按照不同的方式可以分成很多类型的组件:
根据组件的定义方式:函数组件和类组件;
根据组件内部是否有状态需要维护:无状态组件和有状态组件;
根据组件的职责:展示型组件和容器型组件;
组件的定义方式 类组件 类组件定义有下面几个要求:
组件名称必须以大写字符开头;
类组件需要继承自 React.Component;
类组件必须实现 render 函数;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 export default class App extends Component { constructor ( ) { super () this .state = { message: 'hello' } } render ( ) { return ( <div> <h2>{this .state.message}</h2> </div> ) } }
函数式组件 函数式组件使用 function 来定义函数,这个函数会返回和类组件中 render 函数返回一样的内容。
函数式组件有如下几个特点:
没有 this 对象;
没有内部的状态;
没有生命周期函数,但会被更新并挂载。
1 2 3 4 5 export default function App ( ) { return ( <h2>hello</h2> ) }
组件间的通信 父传子 函数式组件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import React, { Component } from 'react' function Child (props ) { const { name, age } = props return ( <div>子组件数据:{"name:" + name + "age:" + age}</div> ) } export default class App extends Component { render ( ) { return ( <div> <Child name="LqZww" age="18" /> </div> ) } }
类组件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Child extends Component { constructor (props ) { super (props) } render ( ) { const { name, age } = this .props return ( <div>子组件数据:{"name:" + name + "age:" + age}</div> ) } } export default class App extends Component { render ( ) { return ( <div> <Child name="LqZww" age="18" /> </div> ) } }
参数验证 在传递数据给子组件时,我们希望对数据进行验证一下,这就需要使用到 propTypes
。
在 React v15.5 起,将 React.PropTypes 移动到一个库中:porp-types
库。
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 27 28 29 30 import React, { Component } from 'react' import PropTypes from 'prop-types' function Child (props ) { const { name, age } = props return ( <div>子组件数据:{"name:" + name + "age:" + age}</div> ) } Child.propTypes = { name: PropTypes.string.isRequired, age: PropTypes.number } Child.defaultProps = { name: 'hhhh' , age: 99 } export default class App extends Component { render ( ) { return ( <div> <Child name="LqZww" age={18 } /> <Child name="qqqq" /> </div> ) } }
上面代码中:
Child.propTypes
:设置数据验证;
PropTypes.string
:该项为 string 类型;
isRequired
: 表示必传项;
Child.defaultProps
: 表示设置默认值;
在类组件中,也可以使用上面这种方法,但还有一种方法可以写:
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 27 28 29 30 31 32 33 34 35 36 import React, { Component } from 'react' import PropTypes from 'prop-types' class Child extends Component { static propTypes = { name: PropTypes.string.isRequired, age: PropTypes.number } static defaultProps = { name: 'hhhh' , age: 99 } constructor (props ) { super (props) } render ( ) { const { name, age } = this .props return ( <div>子组件数据:{"name:" + name + "age:" + age}</div> ) } } export default class App extends Component { render ( ) { return ( <div> <Child name="LqZww" age={18 } /> <Child name="qqqq" /> </div> ) } }
子传父 函数传递 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 27 28 29 30 31 32 33 import React, { Component } from 'react' class CounterButton extends Component { render ( ) { const { onClick } = this .props return <button onClick ={onClick} > +1</button > } } export default class App extends Component { constructor (props ) { super (props) this .state = { counter: 0 } } render ( ) { return ( <div> <h2>当前计数为:{this .state.counter}</h2> <button onClick={e => this .increment()}>+</button> <CounterButton onClick={e => this .increment()} /> </div> ) } increment ( ) { this .setState({ counter: this .state.counter + 1 }) } }
案例 下面我们就来做一个 tab 切换的一个案例。
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 27 28 29 30 31 32 import React, { Component } from 'react' import TabControl from './TabControl' export default class App extends Component { constructor (props ) { super (props) this .titles = ['tab1' , 'tab2' , 'tab3' ] this .state = { currentTitle: "tab1" , } } render ( ) { const { currentTitle } = this .state return ( <div> <TabControl titles={this .titles} itemClick={index => this .itemClick(index)} /> <h2>{currentTitle}</h2> </div> ) } itemClick (index ) { this .setState({ currentTitle: this .titles[index] }) } }
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 import React, { Component } from 'react' import PropTypes from 'prop-types' ;export default class TabControl extends Component { constructor (props ) { super (props) this .state = { currentIndex: 0 } } render ( ) { const { titles } = this .props const { currentIndex } = this .state return ( <div className='tab-control' > { titles.map((item, index ) => { return ( <div key={index} className={'tab-item ' + (index === currentIndex ? 'active' : '' )} onClick={e => this .itemClick(index)} > <span>{item}</span> </div> ) }) } </div> ) } itemClick (index ) { this .setState({ currentIndex: index }) const { itemClick } = this .props itemClick(index) } } TabControl.propTypes = { titles: PropTypes.array.isRequired }
实现Vue中slot功能 方案一(不推荐) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import React, { Component } from 'react' import NavBar from './NavBar' export default class App extends Component { render ( ) { return ( <div> <NavBar> <span>1 </span> <span>2 </span> <span>3 </span> </NavBar> </div> ) } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import React, { Component } from 'react' export default class NavBar extends Component { render ( ) { return ( <div className='nav-bar' > <div className='nav-left' > {this .props.children[0 ]} </div> <div className='nav-center' > {this .props.children[1 ]} </div> <div className='nav-right' > {this .props.children[2 ]} </div> </div> ) } }
这种方式会有个局限性就是必须要按着顺序进行摆放书写。这种一般适用于只有一个插槽数据的时候使用。
方案二(推荐) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import React, { Component } from 'react' import NavBar from './NavBar' export default class App extends Component { render ( ) { return ( <div> <NavBar leftSlot={<span > 1</span > } centerSlot={<span > 2</span > } rightSlot={<span > 3</span > } /> </div> ) } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import React, { Component } from 'react' export default class NavBar extends Component { render ( ) { const { leftSlot, centerSlot, rightSlot } = this .props return ( <div className='nav-bar' > <div className='nav-left' > {leftSlot} </div> <div className='nav-center' > {centerSlot} </div> <div className='nav-right' > {rightSlot} </div> </div> ) } }
跨组件通信 方案一(不推荐) 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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 import React, { Component } from 'react' function ProfileHeader (props ) { return ( <div> <h2>name:{props.name}</h2> <h2>age:{props.age}</h2> </div> ) } function Profile (props ) { return ( <div> <ProfileHeader name={props.name} age={props.age} /> <ul> <li>li1</li> <li>li2</li> <li>li3</li> </ul> </div> ) } export default class App extends Component { constructor (props ) { super (props) this .state = { name: 'LqZww' , age: 18 } } render ( ) { const { name, age } = this .state; return ( <div> <Profile name={name} age={age} /> </div> ) } }
我们可以通过 props
一层一层的传递下去。
以上代码中:
1 2 3 <ProfileHeader name={props.name} age={props.age} /> {} <ProfileHeader {...props} />
这叫做 属性展开 。我们可以使用展开运算符 ...
来在 JSX 中传递整个 props 对象。
方案二(Context) 对于非父子组件的数据共享,如果我们像方案一那样一层一层的传递数据是非常繁琐的,并且代码也是多余的。
在 React 中提供了一个 API:Context。它提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树逐层传递 props。
它设计的目的是为了共享那些对于一个组件树而言是 “全局” 的数据。在当前认证的用户、主题、首选语言这些场景都可以使用。
它有如下几个相关的 API:
React.createContext
创建一个需要共享的 Context 对象;
如果一个组件订阅了 Context,那么这个组件会从离自身最近的那个匹配的 Provider
中读取到当前的 Context 值;
defaultValue
是组件在顶层查找过程中没有找到对应的 Provider
,那么就使用的默认值;
const MyContext = React.createContext(defaultValue);
Context.Provider
每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化;
Provider 接收一个 value 属性,传递给消费组件;
一个 Provider 可以和多个消费组件有对应关系;
多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据;
当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染;
<MyContext.Provider value={} />
;
Class.contextType
挂载在 class 上的 contextType 属性会被重新赋值为一个由 React.createContext()
创建的 Context 对象;
它能让你使用 this.context 来消费最近 Context 上的那个值;
可以在任何 生命周期中访问它,包括 render 函数中;
MyClass.contextType = MyContext;
Context.Consumer
它能订阅到 context 变更,让我们可以在函数式组件中完成订阅 context;
它需要函数作为子元素;
此函数接收当前的 context 值,返回一个 React 节点;
<MyContext.Consumer>{value=>{}}</MyContext.Consumer>
基本使用 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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 import React, { Component } from 'react' const UserContext = React.createContext({ name: '默认名字' , age: 1 }) class ProfileHeader extends Component { render ( ) { return ( <div> <h2>name:{this .context.name}</h2> <h2>age:{this .context.age}</h2> </div> ) } } ProfileHeader.contextType = UserContext function Profile (props ) { return ( <div> <ProfileHeader /> </div> ) } export default class App extends Component { constructor (props ) { super (props) this .state = { name: 'LqZww' , age: 18 } } render ( ) { return ( <div> {} <UserContext.Provider value={this .state}> <Profile /> </UserContext.Provider> </div> ) } }
如果我们把 <Profile />
没有放到 <UserContext.Provider value={this.state}></UserContext.Provider>
里面的话,而是放到外面的,将会展示默认值 。
函数式组件中的使用 上面中我们是在类组件中的使用,下面就来看看在函数式组件中该如何使用:
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 import React, { Component } from 'react' const UserContext = React.createContext({ name: '默认名字' , age: 1 }) function ProfileHeader ( ) { return ( <UserContext.Consumer> { value => { return ( <div> <h2>name:{value.name}</h2> <h2>age:{value.age}</h2> </div> ) } } </UserContext.Consumer> ) } function Profile (props ) { return ( <div> <ProfileHeader /> </div> ) } export default class App extends Component { constructor (props ) { super (props) this .state = { name: 'LqZww' , age: 18 } } render ( ) { return ( <div> <UserContext.Provider value={this .state}> <Profile /> </UserContext.Provider> </div> ) } }
多个context的使用 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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 import React, { Component } from 'react' const UserContext = React.createContext({ name: '默认名字' , age: 1 }) const ThemeContext = React.createContext({ theme: 'white' }) function ProfileHeader ( ) { return ( <UserContext.Consumer> { value => { return ( <ThemeContext.Consumer> { theme => { return ( <div> <h2>name:{value.name}</h2> <h2>age:{value.age}</h2> <h2>主题:{theme.theme}</h2> </div> ) } } </ThemeContext.Consumer> ) } } </UserContext.Consumer> ) } function Profile (props ) { return ( <div> <ProfileHeader /> </div> ) } export default class App extends Component { constructor (props ) { super (props) this .state = { name: 'LqZww' , age: 18 } } render ( ) { return ( <div> <UserContext.Provider value={this .state}> <ThemeContext.Provider value={{ theme : 'black' }}> <Profile /> </ThemeContext.Provider> </UserContext.Provider> </div> ) } }
在开发中,如果我们有多个组件之间需要共享数据,一般不会这样使用的,而是会使用 redux
。
全局事件传递 安装 events
依赖:
使用:
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 import React, { PureComponent } from 'react' ;import { EventEmitter } from 'events' const eventBus = new EventEmitter()class Home extends PureComponent { componentDidMount ( ) { eventBus.addListener('sayHello' , this .handleSayListener) } componentWillUnmount ( ) { eventBus.removeListener('sayHello' , this .handleSayListener) } handleSayListener (name, age ) { console .log(name, age); } render ( ) { return ( <div> home </div> ) } } class Profile extends PureComponent { render ( ) { return ( <div> profile <button onClick={e => this .emmitEvent()}>btn</button> </div> ) } emmitEvent ( ) { eventBus.emit('sayHello' , 'hello home' , 18 ) } } export default class App extends PureComponent { render ( ) { return ( <div> <Home /> <Profile /> </div> ); } }
setState 基本使用 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 import React, { Component } from 'react' export default class App extends Component { constructor (props ) { super (props) this .state = { counter: 0 } } render ( ) { return ( <div> <h2>当前数:{this .state.counter}</h2> <button onClick={e => this .increment()}>+</button> </div> ) } increment ( ) { this .setState({ counter: this .state.counter + 1 }) } }
setState异步更新 当我们在 setState
下面打印 counter
的值,我们会发现当我们点击了一次按钮后,却还是打印的是 0
。这是因为,setState
是一个异步更新的。由此可见,我们不能在执行完 setState
后立即拿到最新的结果。
至于为什么 setState
设置为异步,可以参考此回答
简单的总结就是:
设置为异步可以显著的提高性能;
如果同步更新了 state
,但还没有执行 render 函数,那么 state
与 props
就不能保持同步;
那如果我们要获取更新后的数据该怎么办呢,这里有下面两种方式:
在 setState
的第二个参数(回调函数)中获取;1 2 3 4 5 6 7 increment ( ) { this .setState({ counter: this .state.counter + 1 }, () => { console .log(this .state.counter); }) }
在 componentDidUpdate
生命周期函数中获取;1 2 3 componentDidUpdate ( ) { console .log(this .state.counter); }
注意:componentDidUpdate
的执行顺序是早于 setState
回调函数的。
setState同步更新 在某些情况下 setState
也是同步更新的:
将 setState
放到定时器中使用:1 2 3 4 5 6 7 8 increment ( ) { setTimeout (() => { this .setState({ counter: this .state.counter + 1 }) console .log(this .state.counter); }, 0 ) }
使用原生的 DOM 事件监听:1 2 3 4 5 6 7 8 componentDidMount ( ) { document .getElementById('btn' ).addEventListener('click' , () => { this .setState({ counter: this .state.counter + 1 }) console .log(this .state.counter); }) }
我们先来看看下例代码:
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 import React, { Component } from 'react' export default class App extends Component { constructor (props ) { super (props) this .state = { list: [ { id : 0 , name : 'lq' , age : 18 }, { id : 1 , name : 'zww' , age : 19 } ] } } render ( ) { return ( <div> <ul> { this .state.list.map(item => { return <li key ={item.id} > {item.name}</li > }) } </ul> <button onClick={e => this .addData()}>add</button> </div> ) } shouldComponentUpdate (newProps, newState ) { if (newState.list !== this .state.list) { return true } return false } addData ( ) { const newData = { id : Math .random(), name : 'lqzww' , age : 20 } this .state.list.push(newData) this .setState({ list: this .state.list }) } }
当我们点击按钮后,会发现页面并没有任何变化!这是因为数组是一个引用类型,存储的是一个内存地址。
当我们对 addData
方法做如下更改:
1 2 3 4 5 6 7 8 addData ( ) { const newData = { id : Math .random(), name : 'lqzww' , age : 20 } const newList = [...this.state.list] newList.push(newData) this .setState({ list: newList }) }
我们会发现数据正确的添加了。这是因为使用展开运算符会在内存中新开辟一个空间,并把对象的引用地址全部拷贝到新空间中,此时 newData
指向的是新地址。当把数据 push
进去后是给新地址添加数据,最后旧地址与新地址在进行判断的时候是不相同,就直接返回 true
,并重新调用 render
函数。
如果觉得上面使用 shouldComponentUpdate
的操作很麻烦,我们可以使用继承 PureComponent
,它会自动的对数据进行浅层比较:
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 27 28 29 30 31 32 33 34 35 36 import React, { PureComponent } from 'react' export default class App extends PureComponent { constructor (props ) { super (props) this .state = { list: [ { id : 0 , name : 'lq' , age : 18 }, { id : 1 , name : 'zww' , age : 19 } ] } } render ( ) { return ( <div> <ul> { this .state.list.map(item => { return <li key ={item.id} > {item.name}</li > }) } </ul> <button onClick={e => this .addData()}>add</button> </div> ) } addData ( ) { const newData = { id : Math .random(), name : 'lqzww' , age : 20 } const newList = [...this.state.list] newList.push(newData) this .setState({ list: newList }) } }
React更新机制 React 的渲染流程大致为:JSX -> 虚拟DOM -> 真实DOM;
而更新流程为:props/state改变 -> render函数重新执行 -> 产生新的DOM树 -> 新旧DOM树进行diff -> 计算出差异进行更新 -> 更新到真实DOM;
下面我们来看一个例子:
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 27 28 29 30 31 32 33 import React, { Component } from 'react' class Footer extends Component { render ( ) { console .log('footer 被调用' ); return <h2 > footer</h2 > } } export default class App extends Component { constructor (props ) { super (props) this .state = { counter: 0 } } render ( ) { console .log('app 被调用' ); return ( <div> <h2>当前数:{this .state.counter}</h2> <button onClick={e => this .increment()}>+</button> <Footer /> </div> ) } increment ( ) { this .setState({ counter: this .state.counter + 1 }) } }
我们会发现,当初次进入页面的时候就会打印出两条信息。当我们点击加号时,发现控制台还是打印了两条信息,但是这并不是我们想要的,因为我们只在 App 上更新了数据,并不想 Footer 被调用。如果每次点击全部都重新调用了,那将会很浪费性能。因此,这时候我们可以使用 shouldComponentUpdate
生命周期函数来解决,如下:
1 2 3 4 5 6 7 shouldComponentUpdate (nextProps, nextState ) { console .log(nextProps, nextState); if (this .state.counter != nextState.counter) { return true } return false ; }
我们还可以使用 PureComponent
:
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 27 28 29 30 31 32 33 import React, { Component, PureComponent } from 'react' class Footer extends PureComponent { render ( ) { console .log('footer 被调用' ); return <h2 > footer</h2 > } } export default class App extends Component { constructor (props ) { super (props) this .state = { counter: 0 } } render ( ) { console .log('app 被调用' ); return ( <div> <h2>当前数:{this .state.counter}</h2> <button onClick={e => this .increment()}>+</button> <Footer /> </div> ) } increment ( ) { this .setState({ counter: this .state.counter + 1 }) } }
此时,第一次刷新会全部打印,当点击加号后只会打印 app 被调用
。
除此之外,我们还可以使用 memo
:
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 27 28 29 30 31 import React, { Component, memo } from 'react' const FooterMemo = memo(function Footer ( ) { console .log('footer 被调用' ); return <h2 > footer</h2 > }) export default class App extends Component { constructor (props ) { super (props) this .state = { counter: 0 } } render ( ) { console .log('app 被调用' ); return ( <div> <h2>当前数:{this .state.counter}</h2> <button onClick={e => this .increment()}>+</button> <FooterMemo /> </div> ) } increment ( ) { this .setState({ counter: this .state.counter + 1 }) } }
此时点击加号后也只会打印 app 被调用
。
1.11