学习React

安装

​ creat-react-app 脚手架安装

npm install -g creat-react-app

​ 初始化安装

creat-react-app hello-react

​ 启动

cd hello-react

npm start

使用jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React,{Component} from 'react'
import ReactDOM from 'react-dom'
import './index.css'
class Header extends Component{
render(){
return(
<div>
<h1>hello React</h1>
</div>
)
}
}
ReactDOM.render(
<header/>,
document.getElementById("root")
)

jsx在编译时会变成相应的js对象描述.

react-dom负责把这个js对象描述变成dom元素并渲染.

Reader方法

我们在编写组件时,需要继承react的Component,一个组件类必须实现一个render方法,这个方法返回一个jsx对象,需要注意的是,必须用一个外层的元素把所有内容包裹起来,而不能是几个元素.

表达式插入

在jsx中可以插入js表达式,表达式返回的结果会渲染在页面上,表达式用{}包裹,如果包裹的是一个对象,在对象的外面也要加上{}.

{}内可以放任何js的代码.不仅仅可以放在标签内部,也可以放在标签属性上.

<a className={className}/>

因为class,和for是js关键字,所以在react中用className和htmlFor代替.

条件返回

我们可以根据不同的条件返回不同的jsx.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
render(){
const isGood = true
return (
<div>
<h1>
{
isGood
?<strong>isGood</strong>
:<span>is bad</span>
}
</h1>
</div>
)
}

如果你想要隐藏一个元素,返回一个null即可.

jsx元素变量

jsx元素就是js对象,那么jsx元素其实可以像js对象一样赋值给变量,作为函数参数传递或作为函数返回值.

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
//作为变量
render(){
const isGood = true
const good = <strong>isGood</strong>
const bad = <span>isBad</span>
return(
<div>
<h1>
{isGood?good:bad}
</h1>
</div>
)
}
//作为函数参数传递
renderGood(good,bad){
const isGood = true
return isGood?good:bad
}
render(){
return(
<div>
{this.renderGood(<strong>isGood</strong>,<span>isBad</span>)}
</div>
)
}

组件的组合,嵌套和组件树

自定义的组件必须用大写字母开头,普通html标签用小写字母开头.

组件之间可以组合,嵌套.就像普通的html标签一样使用就可以,这样组合嵌套最后构成一个组件树,来表示它们之间的关系.

事件监听

在react中监听事件甚至需要给监听的元素加上类似于onClickonKeyDown这样的属性,紧跟的是一个表达式插入,这个表达式返回一个实例方法.

在react中不需要调用浏览器原生的addEventListener进行事件监听,react帮我们封装好了一系列的on*的属性,而且不用考虑不同浏览器之间的兼容问题.如果需要用到事件对象event,在函数中传入e参数即可,react把event对象也做了封装.

一般在某个类的实例方法中,this指的就是这个实例本身,但在react中,调用你传给它方法的时候,并不是通过对象方法的方式调用(this.handleclick),而是通过函数调用(handleClick),所以在事件监听函数中的this是null或undefined.当你想在函数中使用当前实例的时候,需要手动将实例方法bind到当前实例再传给react,这种方式在react中非常常见.

这些on*事件只能用在普通的html元素上,不能用在组件标签上

组件的state和setState

一个组件的显示形态是由它的数据状态和配置参数决定的.一个组件可以拥有自己的状态,就像一个点赞按钮,有点赞状态和未点赞状态,并可以再这两种状态之间来回切换.state就是存储这种可变化的状态的.改变状态时不能直接赋值,可以使用setState方法来改变状态.当我们调用setState时,react会更新组件状态,重新调用render方法,然后再把render方法所渲染的最新内容显示到页面上.state方法接受一个对象或函数作为参数如果我们用this.state=XXX,React就没办法知道你修改了组件的状态.

state接收对象参数
1
2
3
4
5
6
7
8
9
10
constructor(props){
super(props)
this.state = {
name: 'tom',
isLicked: false
}
}
handleClick(){
this.setState({isLicked: !this.state.isLicked})
}
state接收函数作为参数

再调用setState时,react不会马上修改state,而是把这个对象放到一个更新队列中,稍后才回从多个队列中把新状态计算合并提取出来合并到state,再触发更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//对象作为参数
handClick(){
this.setState({count:0})//this.state.count还是undefined
this.setState({count: this.state.count+1})//undefined+1=NaN
this.setState({count:this.state.count+2})//NaN+2=NaN
}
//函数作为参数可以接受一个参数作为上次setState的返回值
handClick(){
this.setState((prevState)=>{
return {count:0}//0
})
this.setState((prevState)=>{
return {count: prevState.count +1}//0+1=1
})
this.setState((prevState)=>{
return {count: prevState.count+2}//1+2=3
})
}
//进行3次setState,但组件只会渲染一次.因为react会把所有事件循环中的消息队列中的state合并再渲染.

配置组件的props

一个组件可能在不同的地方用到,所以组件要有一定的可配置性.每个组件都可以接收一个props参数,他是一个对象,包含你对这个组件的配置.

组件内部是通过this.props的方式来获取组件的参数,如果this.props有需要的属性就采用,没有的话就默认.

再使用一个组件的时候,可以把参数放在标签中的属性中,所以属性都会作为props对象的键值.

默认配置defaultProps

我们可以通过||操作符来实现默认配置,const word = this.props.like || '已赞'React也提供了一种方式defaultProps来配置默认配置.

1
2
3
4
5
6
7
8
9
10
class LikeBtn extends Component{
static defaultProps = {
like: '取消',
unlike: '点赞'
}
constructor(){
super()
this.state = {isLike: false}
}
}
props不可变

props一旦传入进来就不能改变.如果我们使用this.props.like='取消'控制台会直接报错.

你不能改变一个组件被渲染时传进来的props,因为如果渲染过程中可以改变会导致组件的显示形态和行为变得不可预测.

但这并不意味这props永远不能修改,组件的使用者可以主动的通过重新渲染的方式把新的props传入到组件中.

1
2
3
4
<div>
<LikeBtn like={this.state.like}>
<button onClick={this.handleClick.bind(this)}><button>
</div>

在这里,我们把state中的数据传给props,但我们点击按钮时,我们使用setState改变state的值,并导致页面重新渲染,改变后的state会传给新的props.

state VS props

state的主要作用是用于组件保存,控制,修改自己的状态.state在组件内部初始化,可以被自身修改,但不能被外界访问和修改.可以把state当做一个局部的只能被自身控制的数据源.通过this.setState进行更新,该方法会导致组件重新渲染.

props主要作用是可以传入参数来配置该组件,组件内部无法控制和修改,除非外部主动传入新的props,否则组件的props永远保持不变.

一个组件的state中的数据可以传给子组件的props,一个组件也可以使用外部传入的props来初始化自己的state.但他们职责非常清晰state是让组件控制自己的状态,props是让外部对组件自己进行配置,尽量少的用state,尽量多的使用props

无状态组件
1
2
3
4
5
6
7
8
const HelloWorld = (props)=>{
const sayHi = (event)=>{
alert("helloWorld")
}
return(
<div onClick={sayHi}>helloWorld</div>
)
}

以前的一个组件时通过继承Component来构建,一个子类就是一个组件,而用函数式编写方式是一个函数就是一个组件,你可以和之前使用使用该组件.不同的是,函数式组件只能接受props而无法和类组件一样在constructor里面初始化state.函数式组件就是一种只接受props和提供render方法的类组件.

渲染列表数据

渲染存放jsx元素的数组
1
2
3
4
5
6
7
8
9
10
11
render(){
return(
<div>
{[
<span>1</span>,
<span>2</span>,
<span>3</span>
]}
</div>
)
}

如果你往{}里放一个数组,react会把数组中的元素依次渲染出来.

使用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
38
39
const users = [
{userName:'tom',age:21,gender:'male'},
{userName:'jerry',age:23,gender:'male'},
{userName:'lily',age:41,gender:'male'},
{userName:'lucy',age:31,gender:'male'},
]
render(){
const userEle = []//保存渲染后的jsx数组
for(let user of users){
userEle.push(
<div>
<span>{user.userName}</span>
<span>{user.age}</span>
<span>{user.gender}</span>
</div>
)
}
return(
<div>{userEle}</div>
)
}
//但我们一般不会手动写循环来构建jsx结构,而是用es6 的map方法
render(){
return(
<div>
{
users.map((user,index,arr)=>{
return(
<div>
<span>{{user.userName}}</span>
<span>{{user.age}}</span>
<span>{{user.gender}}</span>
</div>
)
})
}
</div>
)
}

然后你会发现,react报错了,因为对于用表达式套数组罗列到页面上的元素,都要为每个元素加上key属性,这个key必须是每个元素的标识

状态提升

在编写组件时,当有别的组件依赖或影响某个组件的某个状态state时,我们通常将这种组件之间共享的状态交给组件最近的公共父节点保管,然后通过props把状态传递给子组件,这样就可以在组件之间共享数据了.这种方式在React中被称为状态提升.

如果这个公共的分组件只是组件树下很小的一个子树,我们需要一直把状态提升上去,一旦发生提升,就需要修改原来保存状态以及传递数据的所有代码,这种无限制的提升并不是一个好的方案.

如何更好的管理被多喝组件依赖的状态?React并没有提供更好的解决方案,我们可以引入Redux状态管理工具来帮助我们解决这种共享状态.对于不会被外界依赖和影响的状态,一般只保存在组件内部即可,不需要做提升.

挂载阶段组件的生命周期

我们来看看下面这段代码发生了什么

1
2
3
4
5
6
7
8
9
10
11
12
ReactDOM.render(
<Header/>,
document.getElementById('root')
)
//1.实例化一个Header
const header = new Header(props,children)
//2.调用header.render
const headerJsx = header.render()
//3.构建真正的DOM元素
const headerDom = createDOM(...)
//4. 把DOM元素追加到页面上
document.getElementById('root').appendChild(headerDOm)

上面这个过程称为组件的挂载,这是一个从无到有的过程

React为了更好的掌握组件的挂载过程,提供了一系列等生命周期函数.包括了两个挂载函数.

componentWillMountcomponentDIdMount.当我们在页面渲染后删除了某个元素后,也有对应的函数componentWillUnmount.

他们之间的顺序为

1. constructor  (指向prototype对象所在的构造函数,关于组件自身状态的初始化)
2. component will mount (组件将要挂载,一般组件启动的动作,包括ajax数据的拉取,设置定时器等等在此进行)
3. render (返回jsx元素)
4. component did mount (组件已经挂载,当组件的启动工作依赖dom时,例如动画,就可以放在这里.)
5. component will unmount (组件将要移除,在组件销毁时清除该组件定时器和其他数据清理工作)

更新阶段的组件生命周期

除了挂载阶段,还有一种更新阶段.setState导致react重新渲染组件就是一个组件的变化过程.

  1. shouldComponentUpdate(nextProps,nextState): 你可以通过这个方法控制组件是否重新渲染,如果返回false就不重新渲染,该生命周期在性能优化上非常有用.
  2. componentWillReceiveProps(nextProps):组件从父组件接收到新的props之前调用.
  3. componentWillUpdate():组件重新渲染之前调用.
  4. componentDIdUpdate():重新渲染后调用.

ref 和 React 中的 DOM 操作

React中我们很少和打交道,有一系列的on*方法帮我们进行事件监听,我们不再需要调用addEventListener的DOM API,我们通过setState重新渲染组件,渲染时把新的props传给子组件达到页面更新效果,而不再借用jQuery进行页面更新.

但React并不能满足所有的DOM操作,比如进入页面自动focus到某个输入框,.比如你想获取某个元素的尺寸在做后续动画等等.所以它提供了ref属性帮助我们获取已经挂在的dom节点,你可以给某个JSX元素加上ref属性.

<input ref={(input)=>{this.input = input}}>

我们给input加了一个ref属性,该属性是一个函数,该元素在页面上挂载完毕后调用这个函数,并把这个挂载后的dom节点传给这个函数.我们把元素赋值给组件实例的一个属性,这样就可以通过this.input获取这个DOM元素.

如果给组件挂载ref,那么我们获取的是这个组件在react内部初始化的实例,这并不常用,不建议这样做.

props.children 和容器类组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ReactDOM.render(
<Card>
<h1>I'm H1</h1>
<div>I'm Div</div>
</Card>,
document.getElementById('root')
)
class Card extends Component{
render(){
return{
<div>
{this.props.children}
{this.props.children[0]}
</div>
}
}
}

在使用自定义组件时,可以再组件内部嵌套jsx结构.嵌套的结构可以再组件内部通过props.children获取到,这种组件编写方式在编写容器类型的组件当中非常有用,而在实际React项目中,我们几乎每天都需要用这种方式编写组件.

dangerouslySetHTML 和 style 属性

#####dangerouslySetHTML

出于安全因素(XSS攻击),React会把所有表达式插入的内容都自动转义.类似于jQuery的text().

1
2
3
4
5
const header = '<h1>helloWorld</h1>'
<div>
{header}
</div>
//因为react的自动转义,并不会渲染<h1>元素,而是显示文本形式

如何做到动态设置HTML效果呢?我们可以给元素设置一个dangerouslySetHTML属性传入一个对象,这个对象的__html属性值就相当于innerHTML,就可以动态渲染元素结构了.

1
2
3
4
<div 
dangerouslySetHTML={{__html:'<h1>helloworld</h1>'}}
className="container">
<div>

之所以搞这么复杂是因为设置这个属性可能会导致跨站脚本攻击,不必要的情况就不要使用.

style

普通DOM元素中的style

1
<div style="font-size:14px;color:red;"><div>

React中需要把css属性变为对象再传给元素

1
<h1 style={{fontSize:'14px',color:'red'}}></h1>

style接收一个对象,里面是css属性键值对,原来css带’-‘的属性都需要换成驼峰命名法.我们可以用props或者state中的数据生成样式对象再传给元素,再用setState修改样式,非常灵活.

1
2
<h1 style={{fontSize:'14px',color:this.state.color}}></h1>
this.setState({color:'blue'})

PropTypes和组件参数验证

React提供一种机制,可以给组件的配置参数加上类型验证.我们需要安装React提供的第三方库prop-types

npminstall --save prop-types

1
2
3
4
5
6
7
8
9
10
import React,{ Component } from 'react'
import PropTypes from 'prop-types'
class Card extends Component{
static propTypes = {
text: PropTypes.string.isRequired
}
static defaultProps = {
...
}
}

PropTypes提供的参数有:array,bool,func,number,object,string,node,element…

react规范组件和方法命名

1. static开头的类属性,如`defaultProps`,`propTypes`
2. 构造函数,constructor
3. getter/setter
4. 组件生命周期
5. _开头的私有方法
6. 事件监听方法,handle**
7. render*()表示不同render()内容的函数

高阶组件

高阶组件就是一个函数,传给它一个组件作为函数的参数,它返回一个新的组件.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React,{Component} from 'react'
export default (OldComponent,name)=>{
class NewComponent extends Component{
constructor(){
super()
this.state = {data:null}
}
componentWillMount(){
let data = localStorage.getItem(name)
this.setState({data})
}
render(){
return <OldComponent data={this.state.data}></OldComponent>
}
}
return NewComponent
}

怎么使用这个高阶组件呢?

1
2
3
4
5
6
7
8
import NewComponent from './NewComponent'
class InputName extends Component{
render(){
return <input value={this.props.data}>
}
}
InputName = NewComponent(InputName,'username')
export default InputName

其实高阶组件就是为了组件之间的代码复用.组件可能有着相同的逻辑,把这些逻辑抽取出来,放在高阶组件里进行复用.高阶组件内部包装的组件和被包装的组件通过props传递数据.

context

context(上下文)是React中一个比较特殊的东西.某个组件只要往自己的context里面放一些状态,这个组件下的所有子组件都可以直接访问而不用通过中间组件一层层传递,它的父组件则不能访问到.

context打破了组件之间通过props传递数据的规范,增强了组件间的耦合性.就像全局变量一样,每个组件都能随意访问和修改,这会让程序运行不可预料.

一些第三方状态管理的库就是充分利用了这种机制给我们提供了极大地便利,所以我们一般不手写context,也不要用它,需要时用这些第三方的应用状态管理库即可.

本文参考胡子大哈的React小书,详情请点击