元素与组件
如果代码多了之后,不可能一直在render方法里写,所以就需要把里面的代码提出来,定义一个变量,像这样:
import React from 'react'
import ReactDOM from 'react-dom'
// 这里感觉又不习惯了?这是在用JSX定义一下react元素
const app = <h1>欢迎进入React的世界</h1>
ReactDOM.render(
app,
document.getElementById('root')
)
函数式组件
由于元素没有办法传递参数,所以我们就需要把之前定义的变量改为一个方法,让这个方法去return一个元素:
import React from 'react'
import ReactDOM from 'react-dom'
// 特别注意这里的写法,如果要在JSX里写js表达式(只能是表达式,不能流程控制),就需要加 {},包括注释也是一样,并且可以多层嵌套
const app = (props) => <h1>欢迎进入{props.name}的世界</h1>
ReactDOM.render(
app({
name: 'react'
}),
document.getElementById('root')
)
这里我们定义的方法实际上也是react定义组件的第一种方式-定义函数式组件,这也是无状态组件。但是这种写法不符合react的jsx的风格,更好的方式是使用以下方式进行改造
import React from 'react'
import ReactDOM from 'react-dom'
const App = (props) => <h1>欢迎进入{props.name}的世界</h1>
ReactDOM.render(
// React组件的调用方式
<App name="react" />,
document.getElementById('root')
)
这样一个完整的函数式组件就定义好了。但要注意!注意!注意!组件名必须大写,否则报错。
class组件
ES6的加入让JavaScript直接支持使用class来定义一个类,react的第二种创建组件的方式就是使用的类的继承,ES6 class
是目前官方推荐的使用方式,它使用了ES6标准语法来构建,看以下代码:
import React from 'react'
import ReactDOM from 'react-dom'
class App extends React.Component {
render () {
return (
// 注意这里得用this.props.name, 必须用this.props
<h1>欢迎进入{this.props.name}的世界</h1>
)
}
}
ReactDOM.render(
<App name="react" />,
document.getElementById('root')
)
运行结果和之前完全一样,因为JS里没有真正的class,这个class只是一个语法糖, 但二者的运行机制底层运行机制不一样。
-
函数式组件是直接调用, 在前面的代码里已经有看到
-
es6 class
组件其实就是一个构造器,每次使用组件都相当于在实例化组件,像这样:import React from 'react' import ReactDOM from 'react-dom' class App extends React.Component { render () { return ( <h1>欢迎进入{this.props.name}的世界</h1> ) } } const app = new App({ name: 'react' }).render() ReactDOM.render( app, document.getElementById('root') )
组件的组合、嵌套
将一个组件渲染到某一个节点里的时候,会将这个节点里原有内容覆盖
组件嵌套的方式就是将子组件写入到父组件的模板中去,且react没有Vue中的内容分发机制(slot),所以我们在一个组件的模板中只能看到父子关系
// 从 react 的包当中引入了 React 和 React.js 的组件父类 Component
// 还引入了一个React.js里的一种特殊的组件 Fragment
import React, { Component, Fragment } from 'react'
import ReactDOM from 'react-dom'
class Title extends Component {
render () {
return (
<h1>欢迎进入React的世界</h1>
)
}
}
class Content extends Component {
render () {
return (
<p>React.js是一个构建UI的库</p>
)
}
}
/** 由于每个React组件只能有一个根节点,所以要渲染多个组件的时候,需要在最外层包一个容器,如果使用div, 会生成多余的一层dom
class App extends Component {
render () {
return (
<div>
<Title />
<Content />
</div>
)
}
}
**/
// 如果不想生成多余的一层dom可以使用React提供的Fragment组件在最外层进行包裹
class App extends Component {
render () {
return (
<Fragment>
<Title />
<Content />
</Fragment>
)
}
}
ReactDOM.render(
<App/>,
document.getElementById('root')
)
JSX 原理
要明白JSX的原理,需要先明白如何用 JavaScript 对象来表现一个 DOM 元素的结构?
看下面的DOM结构
<div class='app' id='appRoot'>
<h1 class='title'>欢迎进入React的世界</h1>
<p>
React.js 是一个帮助你构建页面 UI 的库
</p>
</div>
上面这个 HTML 所有的信息我们都可以用 JavaScript 对象来表示:
{
tag: 'div',
attrs: { className: 'app', id: 'appRoot'},
children: [
{
tag: 'h1',
attrs: { className: 'title' },
children: ['欢迎进入React的世界']
},
{
tag: 'p',
attrs: null,
children: ['React.js 是一个构建页面 UI 的库']
}
]
}
但是用 JavaScript 写起来太长了,结构看起来又不清晰,用 HTML 的方式写起来就方便很多了。
于是 React.js 就把 JavaScript 的语法扩展了一下,让 JavaScript 语言能够支持这种直接在 JavaScript 代码里面编写类似 HTML 标签结构的语法,这样写起来就方便很多了。编译的过程会把类似 HTML 的 JSX 结构转换成 JavaScript 的对象结构。
下面代码:
import React from 'react'
import ReactDOM from 'react-dom'
class App extends React.Component {
render () {
return (
<div className='app' id='appRoot'>
<h1 className='title'>欢迎进入React的世界</h1>
<p>
React.js 是一个构建页面 UI 的库
</p>
</div>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
)
编译之后将得到这样的代码:
import React from 'react'
import ReactDOM from 'react-dom'
class App extends React.Component {
render () {
return (
React.createElement(
"div",
{
className: 'app',
id: 'appRoot'
},
React.createElement(
"h1",
{ className: 'title' },
"欢迎进入React的世界"
),
React.createElement(
"p",
null,
"React.js 是一个构建页面 UI 的库"
)
)
)
}
}
ReactDOM.render(
React.createElement(App),
document.getElementById('root')
)
React.createElement
会构建一个 JavaScript 对象来描述你 HTML 结构的信息,包括标签名、属性、还有子元素等, 语法为
React.createElement(
type,
[props],
[...children]
)
所谓的 JSX 其实就是 JavaScript 对象,所以使用 React 和 JSX 的时候一定要经过编译的过程:
JSX —使用react构造组件,bable进行编译—> JavaScript对象 —
ReactDOM.render()
—>DOM元素 —>插入页面
#组件中DOM样式
- 行内样式
想给虚拟dom添加行内样式,需要使用表达式传入样式对象的方式来实现:
// 注意这里的两个括号,第一个表示我们在要JSX里插入JS了,第二个是对象的括号
<p style={{color:'red', fontSize:'14px'}}>你好</p>
行内样式需要写入一个样式对象,而这个样式对象的位置可以放在很多地方,例如render
函数里、组件原型上、外链js文件中
- 使用
class
React推荐我们使用行内样式,因为React觉得每一个组件都是一个独立的整体
其实我们大多数情况下还是大量的在为元素添加类名,但是需要注意的是,class
需要写成className
(因为毕竟是在写类js代码,会收到js规则的现在,而class
是关键字)
<p className="hello" style = {this.style}>Hello world</p>
- css-in-js
styled-components
是针对React写的一套css-in-js框架,简单来讲就是在js中写css。npm链接
组件的数据挂载方式
属性(props)
props
是正常是外部传入的,组件内部也可以通过一些方式来初始化的设置,属性不能被组件自己更改,但是你可以通过父组件主动重新渲染的方式来传入新的 props
属性是描述性质、特点的,组件自己不能随意更改。
之前的组件代码里面有props
的简单使用,总的来说,在使用一个组件的时候,可以把参数放在标签的属性当中,所有的属性都会作为组件 props
对象的键值。通过箭头函数创建的组件,需要通过函数的参数来接收props
:
import React, { Component, Fragment } from 'react'
import ReactDOM from 'react-dom'
class Title extends Component {
render () {
return (
<h1>欢迎进入{this.props.name}的世界</h1>
)
}
}
const Content = (props) => {
return (
<p>{props.name}是一个构建UI的库</p>
)
}
class App extends Component {
render () {
return (
<Fragment>
<Title name="React" />
<Content name="React.js" />
</Fragment>
)
}
}
ReactDOM.render(
<App/>,
document.getElementById('root')
)
设置组件的默认props
import React, { Component, Fragment } from 'react'
import ReactDOM from 'react-dom'
class Title extends Component {
// 使用类创建的组件,直接在这里写static方法,创建defaultProps
static defaultProps = {
name: 'React'
}
render () {
return (
<h1>欢迎进入{this.props.name}的世界</h1>
)
}
}
const Content = (props) => {
return (
<p>{props.name}是一个构建UI的库</p>
)
}
// 使用箭头函数创建的组件,需要在这个组件上直接写defaultProps属性
Content.defaultProps = {
name: 'React.js'
}
class App extends Component {
render () {
return (
<Fragment>
{/* 由于设置了defaultProps, 不传props也能正常运行,如果传递了就会覆盖defaultProps的值 */}
<Title />
<Content />
</Fragment>
)
}
}
ReactDOM.render(
<App/>,
document.getElementById('root')
)
props.children
我们知道使用组件的时候,可以嵌套。要在自定义组件的使用嵌套结构,就需要使用 props.children
。在实际的工作当中,我们几乎每天都需要用这种方式来编写组件。
import React, { Component, Fragment } from 'react'
import ReactDOM from 'react-dom'
class Title extends Component {
render () {
return (
<h1>欢迎进入{this.props.children}的世界</h1>
)
}
}
const Content = (props) => {
return (
<p>{props.children}</p>
)
}
class App extends Component {
render () {
return (
<Fragment>
<Title>React</Title>
<Content><i>React.js</i>是一个构建UI的库</Content>
</Fragment>
)
}
}
ReactDOM.render(
<App/>,
document.getElementById('root')
)
状态(state)
状态就是组件描述某种显示情况的数据,由组件自己设置和更改,也就是说由组件自己维护,使用状态的目的就是为了在不同的状态下使组件的显示不同(自己管理)
- 定义state
第一种方式
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
class App extends Component {
state = {
name: 'React',
isLiked: false
}
render () {
return (
<div>
<h1>欢迎来到{this.state.name}的世界</h1>
<button>
{
this.state.isLiked ? '❤️取消' : '🖤收藏'
}
</button>
</div>
)
}
}
ReactDOM.render(
<App/>,
document.getElementById('root')
)
另一种方式(推荐)
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
class App extends Component {
constructor() {
super()
this.state = {
name: 'React',
isLiked: false
}
}
render () {
return (
<div>
<h1>欢迎来到{this.state.name}的世界</h1>
<button>
{
this.state.isLiked ? '❤️取消' : '🖤收藏'
}
</button>
</div>
)
}
}
ReactDOM.render(
<App/>,
document.getElementById('root')
)
this.props
和this.state
是纯js对象,在vue中,data属性是利用Object.defineProperty
处理过的,更改data的数据的时候会触发数据的getter
和setter
,但是React中没有做这样的处理,如果直接更改的话,react是无法得知的,所以,需要使用特殊的更改状态的方法setState
。
- setState
isLiked
存放在实例的 state
对象当中,组件的 render
函数内,会根据组件的 state
的中的isLiked
不同显示“取消”或“收藏”内容。下面给 button
加上了点击的事件监听。
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
class App extends Component {
constructor() {
super()
this.state = {
name: 'React',
isLiked: false
}
}
handleBtnClick = () => {
this.setState({
isLiked: !this.state.isLiked
})
}
render () {
return (
<div>
<h1>欢迎来到{this.state.name}的世界</h1>
<button onClick={this.handleBtnClick}>
{
this.state.isLiked ? '❤️取消' : '🖤收藏'
}
</button>
</div>
)
}
}
ReactDOM.render(
<App/>,
document.getElementById('root')
)
setState
有两个参数
第一个参数可以是对象,也可以是方法return一个对象,我们把这个参数叫做updater
-
参数是对象
this.setState({ isLiked: !this.state.isLiked })
-
参数是方法
this.setState((prevState, props) => { return { isLiked: !prevState.isLiked } })
注意的是这个方法接收两个参数,第一个是上一次的state, 第二个是props
setState
是异步的,所以想要获取到最新的state,没有办法获取,就有了第二个参数,这是一个可选的回调函数
this.setState((prevState, props) => {
return {
isLiked: !prevState.isLiked
}
}, () => {
console.log('回调里的',this.state.isLiked)
})
console.log('setState外部的',this.state.isLiked)
受控组件与非受控组件
React组件的数据渲染是否被调用者传递的props
完全控制,控制则为受控组件,否则非受控组件。
渲染数据
-
条件渲染
{ condition ? '❤️取消' : '🖤收藏' }
-
列表渲染
// 数据
const people = [{
id: 1,
name: 'Leo',
age: 35
}, {
id: 2,
name: 'XiaoMing',
age: 16
}]
// 渲染列表
{
people.map(person => {
return (
<dl key={person.id}>
<dt>{person.name}</dt>
<dd>age: {person.age}</dd>
</dl>
)
})
}
React的高效依赖于所谓的 Virtual-DOM,尽量不碰 DOM。对于列表元素来说会有一个问题:元素可能会在一个列表中改变位置。要实现这个操作,只需要交换一下 DOM 位置就行了,但是React并不知道其实我们只是改变了元素的位置,所以它会重新渲染后面两个元素(再执行 Virtual-DOM ),这样会大大增加 DOM 操作。但如果给每个元素加上唯一的标识,React 就可以知道这两个元素只是交换了位置,这个标识就是key
,这个 key
必须是每个元素唯一的标识
- dangerouslySetInnerHTML
对于富文本创建的内容,后台拿到的数据是这样的:
content = "<p>React.js是一个构建UI的库</p>"
处于安全的原因,React当中所有表达式的内容会被转义,如果直接输入,标签会被当成文本。这时候就需要使用dangerouslySetHTML
属性,它允许我们动态设置innerHTML
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
class App extends Component {
constructor() {
super()
this.state = {
content : "<p>React.js是一个构建UI的库</p>"
}
}
render () {
return (
<div
// 注意这里是两个下下划线 __html
dangerouslySetInnerHTML={{__html: this.state.content}}
/>
)
}
}
ReactDOM.render(
<App/>,
document.getElementById('root')
)
事件处理
绑定事件
采用on+事件名的方式来绑定一个事件,注意,这里和原生的事件是有区别的,原生的事件全是小写onclick
, React里的事件是驼峰onClick
,React的事件并不是原生事件,而是合成事件。
Event 对象
和普通浏览器一样,事件handler会被自动传入一个 event
对象,这个对象和普通的浏览器 event
对象所包含的方法和属性都基本一致。不同的是 React中的 event
对象并不是浏览器提供的,而是它自己内部所构建的。它同样具有event.stopPropagation
、event.preventDefault
这种常用的方法
事件的参数传递
- 在
render
里调用方法的地方外面包一层箭头函数 - 在
render
里通过this.handleEvent.bind(this, 参数)
这样的方式来传递 - 通过
event
传递 - 比较推荐的是做一个子组件, 在父组件中定义方法,通过
props
传递到子组件中,然后在子组件件通过this.props.method
来调用
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
class App extends Component {
constructor() {
super()
this.state = {
xing: '',
ming: ''
}
}
handleInputChange = (e) => {
this.setState({
[e.target.name]: e.target.value
})
}
render () {
const {
xing,
ming
} = this.state
return (
<div>
<label>
<span>姓:</span>
<input
type="text"
name="xing"
value={xing}
onChange={this.handleInputChange}
/>
</label>
<label>
<span>名:</span>
<input
type="text"
name="ming"
value={ming}
onChange={this.handleInputChange}
/>
</label>
<p>欢迎您: {xing}{ming}</p>
</div>
)
}
}
ReactDOM.render(
<App/>,
document.getElementById('root')
)
组件的生命周期
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8tBZyMtj-1600353087265)(/Users/felix/Desktop/workspace/React.js/markdown/images/lifecycle-1.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GdKnvdr6-1600353087268)(/Users/felix/Desktop/workspace/React.js/markdown/images/lifycycle-2.png)]
https://www.jianshu.com/p/514fe21b9914
React中组件也有生命周期,也就是说也有很多钩子函数供我们使用, 组件的生命周期,我们会分为四个阶段,初始化、运行中、销毁、错误处理(16.3之后)
- 初始化
在组件初始化阶段会执行
- constructor
- static getDerivedStateFromProps()
- componentWillMount() / UNSAFE_componentWillMount()
- render()
- componentDidMount()
- 更新阶段
props
或state
的改变可能会引起组件的更新,组件重新渲染的过程中会调用以下方法:
- componentWillReceiveProps() / UNSAFE_componentWillReceiveProps()
- static getDerivedStateFromProps()
- shouldComponentUpdate()
- componentWillUpdate() / UNSAFE_componentWillUpdate()
- render()
- getSnapshotBeforeUpdate()
- componentDidUpdate()
- 卸载阶段
- componentWillUnmount()
- 错误处理
- componentDidCatch()
各生命周期详解
- 1.constructor(props)
React组件的构造函数在挂载之前被调用。在实现React.Component
构造函数时,需要先在添加其他内容前,调用super(props)
,用来将父组件传来的props
绑定到这个类中,使用this.props
将会得到。
官方建议不要在constructor
引入任何具有副作用和订阅功能的代码,这些应当使用componentDidMount()
。
constructor
中应当做些初始化的动作,如:初始化state
,将事件处理函数绑定到类实例上,但也不要使用setState()
。如果没有必要初始化state或绑定方法,则不需要构造constructor
,或者把这个组件换成纯函数写法。
constructor(props) {
super(props);
this.state = {
isLiked: props.isLiked
};
}
- 2.getDerivedStateFromProps(nextProps, prevState)
getDerivedStateFromProps
是react16.3之后新增,在组件实例化后,和接受新的props
后被调用。他必须返回一个对象来更新状态,或者返回null表示新的props不需要任何state的更新。
如果是由于父组件的props
更改,所带来的重新渲染,也会触发此方法。
调用setState()
不会触发getDerivedStateFromProps()
。
之前这里都是使用constructor
+componentWillRecieveProps
完成相同的功能的
- -3. componentWillMount() / UNSAFE_componentWillMount()
UNSAFE_componentWillMount()
在组件挂载前被调用,在这个方法中调用setState()
不会起作用,是由于他在render()
前被调用。
为了避免副作用和其他的订阅,官方都建议使用componentDidMount()
代替。这个方法是用于在服务器渲染上的唯一方法。这个方法因为是在渲染之前被调用,也是惟一一个可以直接同步修改state的地方。
- 4.render()
render()方法是必需的。当他被调用时,他将计算this.props
和this.state
,并返回以下一种类型:
- React元素。通过jsx创建,既可以是dom元素,也可以是用户自定义的组件。
- 字符串或数字。他们将会以文本节点形式渲染到dom中。
- Portals。react 16版本中提出的新的解决方案,可以使组件脱离父组件层级直接挂载在DOM树的任何位置。
- null,什么也不渲染
- 布尔值。也是什么都不渲染。
createPortal用法
render() {
// React does *not* create a new div. It renders the children into `domNode`.
// `domNode` is any valid DOM node, regardless of its location in the DOM.
return ReactDOM.createPortal(
app,//React元素
domNode,//要插入的dom节点
);
}
当返回null
,false
,ReactDOM.findDOMNode(this)
将会返回null,什么都不会渲染。
render()
方法必须是一个纯函数,他不应该改变state
,也不能直接和浏览器进行交互,应该将事件放在其他生命周期函数中。
如果shouldComponentUpdate()
返回false
,render()
不会被调用。
- -5. componentDidMount
componentDidMount
在组件被装配后立即调用。初始化使得DOM节点应该进行到这里。
通常在这里进行ajax请求
如果要初始化第三方的dom库,也在这里进行初始化。只有到这里才能获取到真实的dom.
- 6.componentWillReceiveProps()/UNSAFE_componentWillReceiveProps(nextProps)
官方建议使用getDerivedStateFromProps
函数代替componentWillReceiveProps
。当组件挂载后,接收到新的props
后会被调用。如果需要更新state
来响应props
的更改,则可以进行this.props
和nextProps
的比较,并在此方法中使用this.setState()
。
如果父组件会让这个组件重新渲染,即使props
没有改变,也会调用这个方法。
React不会在组件初始化props时调用这个方法。调用this.setState
也不会触发。
- 7.shouldComponentUpdate(nextProps, nextState)
调用shouldComponentUpdate
使React知道,组件的输出是否受state
和props
的影响。默认每个状态的更改都会重新渲染,大多数情况下应该保持这个默认行为。
在渲染新的props
或state
前,shouldComponentUpdate
会被调用。默认为true
。这个方法不会在初始化时被调用,也不会在forceUpdate()
时被调用。返回false
不会阻止子组件在state
更改时重新渲染。
如果shouldComponentUpdate()
返回false
,componentWillUpdate
,render
和componentDidUpdate
不会被调用。
官方并不建议在
shouldComponentUpdate()
中进行深度查询或使用JSON.stringify()
,他效率非常低,并且损伤性能。
- 8.UNSAFE_componentWillUpdate(nextProps, nextState)
在渲染新的state
或props
时,UNSAFE_componentWillUpdate
会被调用,将此作为在更新发生之前进行准备的机会。这个方法不会在初始化时被调用。
不能在这里使用this.setState(),也不能做会触发视图更新的操作。如果需要更新state
或props
,调用getDerivedStateFromProps
。
- 9.getSnapshotBeforeUpdate()
在react render()
后的输出被渲染到DOM之前被调用。它使您的组件能够在它们被潜在更改之前捕获当前值(如滚动位置)。这个生命周期返回的任何值都将作为参数传递给componentDidUpdate()。
- 10.componentDidUpdate(prevProps, prevState, snapshot)
在更新发生后立即调用componentDidUpdate()
。此方法不用于初始渲染。当组件更新时,将此作为一个机会来操作DOM。只要您将当前的props与以前的props进行比较(例如,如果props没有改变,则可能不需要网络请求),这也是做网络请求的好地方。
如果组件实现getSnapshotBeforeUpdate()
生命周期,则它返回的值将作为第三个“快照”参数传递给componentDidUpdate()
。否则,这个参数是undefined
。
- 11.componentWillUnmount()
在组件被卸载并销毁之前立即被调用。在此方法中执行任何必要的清理,例如使定时器无效,取消网络请求或清理在componentDidMount
中创建的任何监听。
- 12.componentDidCatch(error, info)
错误边界是React组件,可以在其子组件树中的任何位置捕获JavaScript错误,记录这些错误并显示回退UI,而不是崩溃的组件树。错误边界在渲染期间,生命周期方法以及整个树下的构造函数中捕获错误。
PureComponent
PureComponnet
里如果接收到的新属性或者是更改后的状态和原属性、原状态相同的话,就不会去重新render了
在里面也可以使用shouldComponentUpdate
,而且。是否重新渲染以shouldComponentUpdate
的返回值为最终的决定因素。
import React, { PureComponent } from 'react'
class YourComponent extends PureComponent {
……
}
ref
React提供的这个ref
属性,表示为对组件真正实例的引用,其实就是ReactDOM.render()
返回的组件实例,ref
可以挂载到组件上也可以是dom元素上。
- 挂到组件(
class
声明的组件)上的ref表示对组件实例的引用。不能在函数式组件上使用 ref 属性,因为它们没有实例: - 挂载到dom元素上时表示具体的dom元素节点。
在React 新的版本中,要使用ref
, 需要使用React.createRef
方法先生成一个ref
。
import React, { Component, createRef } from 'react'
import ReactDOM from 'react-dom'
class App extends Component {
constructor() {
super()
// 创建inputRef
this.inputRef=createRef()
}
componentDidMount () {
console.log(this.inputRef.current) // <input type="text">
}
render () {
return (
<div>
{/* 关联ref和dom */}
<input type="text" ref={this.inputRef} />
</div>
)
}
}
ReactDOM.render(
<App/>,
document.getElementById('root')
)
组件通信
父组件与子组件通信
- 父组件将自己的状态传递给子组件,子组件当做属性来接收,当父组件更改自己状态的时候,子组件接收到的属性就会发生改变
- 父组件利用
ref
对子组件做标记,通过调用子组件的方法以更改子组件的状态,也可以调用子组件的方法…
子组件与父组件通信
- 父组件将自己的某个方法传递给子组件,在方法里可以做任意操作,比如可以更改状态,子组件通过
this.props
接收到父组件的方法后调用。
跨组件通信
在react没有类似vue中的事件总线来解决这个问题,我们只能借助它们共同的父级组件来实现,将非父子关系装换成多维度的父子关系。react提供了context
api来实现跨组件通信, React 16.3之后的context
api较之前的好用。
// counterContext.js
import React, { Component, createContext } from 'react'
const {
Provider,
Consumer: CountConsumer
} = createContext()
class CountProvider extends Component {
constructor () {
super()
this.state = {
count: 1
}
}
increaseCount = () => {
this.setState({
count: this.state.count + 1
})
}
decreaseCount = () => {
this.setState({
count: this.state.count - 1
})
}
render() {
return (
<Provider value={{
count: this.state.count,
increaseCount: this.increaseCount,
decreaseCount: this.decreaseCount
}}
>
{this.props.children}
</Provider>
)
}
}
export {
CountProvider,
CountConsumer
}
// 定义CountButton组件
const CountButton = (props) => {
return (
<CountConsumer>
// consumer的children必须是一个方法
{
({ increaseCount, decreaseCount }) => {
const { type } = props
const handleClick = type === 'increase' ? increaseCount : decreaseCount
const btnText = type === 'increase' ? '+' : '-'
return <button onClick={handleClick}>{btnText}</button>
}
}
</CountConsumer>
)
}
// 定义count组件,用于显示数量
const Count = (prop) => {
return (
<CountConsumer>
{
({ count }) => {
return <span>{count}</span>
}
}
</CountConsumer>
)
}
// 组合
class App extends Component {
render () {
return (
<CountProvider>
<CountButton type='decrease' />
<Count />
<CountButton type='increase' />
</CountProvider>
)
}
}
复杂的非父子组件通信在react中很难处理,多组件间的数据共享也不好处理,在实际的工作中我们会使用flux、redux、mobx来实现
HOC(高阶组件)
Higher-Order Components就是一个函数,传给它一个组件,它返回一个新的组件。
const NewComponent = higherOrderComponent(YourComponent)
比如,我们想要我们的组件通过自动注入一个版权信息。
// withCopyright.js 定义一个高阶组件
import React, { Component, Fragment } from 'react'
const withCopyright = (WrappedComponent) => {
return class NewComponent extends Component {
render() {
return (
<Fragment>
<WrappedComponent />
<div>©wwming </div>
</Fragment>
)
}
}
}
export default withCopyright
// 使用方式
import withCopyright from './withCopyright'
class App extends Component {
render () {
return (
<div>
<h1>Awesome React</h1>
<p>React.js是一个构建用户界面的库</p>
</div>
)
}
}
const CopyrightApp = withCopyright(App)
这样只要我们有需要用到版权信息的组件,都可以直接使用withCopyright这个高阶组件包裹即可。
React Hooks
React Hooks 是 React 16.7.0-alpha
版本推出的新特性, 有了React Hooks,在 react 函数组件中,也可以使用类组件(classes components)的 state 和 组件生命周期。通过下面几个例子来学习React Hooks。
- State Hook
// useState是react包提供的一个方法
import React, { useState } from "react";
import ReactDOM from "react-dom";
const Counter = () => {
// useState 这个方法可以为我们的函数组件拥有自己的state,它接收一个用于初始 state 的值,返回一对变量。这里我们把计数器的初始值设置为0, 方法都是以set开始
const [count, setCount] = useState(0);
return (
<div>
<p>你点击了{count}次</p>
<button onClick={() => setCount(count + 1)}>点击</button>
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<Counter />, rootElement);
- Effect Hook
// useState是react包提供的一个方法
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
const Counter = () => {
// useState 这个方法可以为我们的函数组件拥有自己的state,它接收一个用于初始 state 的值,返回一对变量。这里我们把计数器的初始值设置为0, 方法都是以set开始
const [count, setCount] = useState(0);
// 类似于componentDidMount或者componentDidUpdate:
useEffect(() => {
// 更改网页的标题,还可以做其它的监听
document.title = `你点击了${count}次`;
});
return (
<div>
<p>你点击了{count}次</p>
<button onClick={() => setCount(count + 1)}>点击</button>
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<Counter />, rootElement);
- React Hooks 的规则
- 只能在顶层调用Hooks。不要在循环,条件或嵌套函数中调用Hook。
- 不要从常规JavaScript函数中调用Hook。只在React函数式组件调用Hooks。
- 自定义hooks可以选择讲解
- react 内置hooks api