前言
首先我说说我为什么学React,React可以说是三大框架中思想最为先进的一款,在国内Vue虽然很火,但是Vue也开始引入了React的一些思想,比如虚拟DOM,接着Vue3.0还会引入JSX,React非常注重组件化和复用性,学习React可以比以前更熟悉一些架构知识。
工作时间也有3个多月了,该学会写东西总结了,最近利用工作时间学习React,今天又有空写一下总结,怕过了一段时间忘记,这篇文章我是看了 React小书 总结下来的,如果有人跟我一样是React入门的话,建议去React小书学起,从脚手架学起真的是快很多,我们老师教我们学东西首先要学会用,再学原理。
这篇文章不适合大神看,如果有大神想指导可以在下面发布评论。
概念
React是个帮你构建页面UI的库,他相当于MVC里面的View,React将我们界面分成各个独立的小块,每个小块都是一个组件,而这些组件可以互相嵌套丶结合,然后就形成了页面
脚手架安装(Create-react-app)
由于React要配合其他工具和库辅助,例如编译需要babel,组织代码需要redux等第三方状态管理工具,写单页面应用需要应用到React-router,这也相当于React的全家桶。
-
使用npm安装Create-react-app
npm install -g create-react-app
-
使用create-react-app创建项目
我第一次使用时报了一个这样的错误,然后我上网查才知道是使用脚手架创建项目时要下载许多依赖文件,然后它是默认使用npm下载的,这时候主要将create-react-app my-app
npm
切换成cnpm
就可以解决了。npm config set registry https://registry.npm.taobao.org
//将npm切换成cnpm- 还有一解决方法就是找到
resource
下面的app里面的extensions
打开后看到有git文件夹,删除,重启。
创建完成之后然后
cd
到项目根目录然后npm start
就可以启动项目了 -
项目结构
创建完成之后
cd
到创建的目录然后可以看到脚手架帮我们创建的结构node_modules public scripts src 依赖文件 静态库 项目启动配置 开发目录 开发时候在src目录上开发就行了,如果要查看项目配置,可以输入
npm run eject
把config
目录暴露出来,这个是默认隐藏的,你也可以在node_modules
目录上找到,这个目录是项目的配置。如果运行npm run eject
报错的话,把代码储存到本地就行了git add .
,然后git commit -m ''
,接着重新运行指令,这执行是不可逆的。在
public
有个index.html
这是整个项目的页面,之后我们要把写好的东西渲染到里面去。src
可以看到一个index.js
,由于写React几乎都是组件,而index
一般用来渲染其他组件
编写React
首先我们把index.js
的代码改成这样再看看效果
import React, { Component } from 'react' // 写React必须要引入这两样东西
import ReactDOM from 'react-dom' // 组件不需要引入,他只有一个作用,就是渲染DOM
import './index.css' // 可以直接引入css或者是sass,也可以引入图片
<!-- 使用组件继承Component类 -->
class Header extends Component {
render () { // render方法把里面东西jsx返回
return (
<!-- jsx -->
<div>
<h1>React大法好</h1>
</div>
)
}
}
<!-- 渲染出去 -->
ReactDOM.render(
<Header />,
document.getElementById('root')
)
复制代码
-
JSX
JSX 是 JavaScript 语言的一种语法扩展,长得像 HTML,但并不是 HTML。
平时我们写
html
是这样写的<div class='box' id='content'> <div class='title'>Hello</div> <button>Click</button> </div> 复制代码
如果我们用
JS对象
来表示的话{ tag: 'div', attrs: { className: 'box', id: 'content'}, children: [ { tag: 'div', arrts: { className: 'title' }, children: ['Hello'] }, { tag: 'button', attrs: null, children: ['Click'] } ] } 复制代码
没错,其实
JSX
也相当于JS对象
,React也会根据这样编译,转化为HTML结构之后就可以拿去构建真正的DOM,这是最后那段渲染DOM所做的事情ReactDOM.render( <Header />, document.getElementById('root') ) 复制代码
这是整个过程流程图,单独把ReactDOM抽出来的原因是因为我们可以渲染到Canvas或者转化原生App(ReactNative)
-
组件render方法
React中一切皆为组件,写组件的时候一般都需要继承React.js的
Component
,这个方法必须返回一个JSX
元素,而返回并列JSX
元素是不合法的<!-- bad --> render() { return { <div>one</div> <div>two</div> } } 复制代码
必须要用一个外层元素包起来,不能有多个外层元素
<!-- good --> render() { return { <div> <div>one</div> <div>two</div> </div> } } 复制代码
-
表达式插入
表达式可以插入变量,还可以计算,还可以条件返回,并且可以写函数,render会把真实的内容返回,特别的灵活
render() { const word = 'word' const isGoodWord = true return( <div> <h1>HELLO {word}</h1> // 变量 <h1>{1 + 2}</h1> // 计算 <h1>{(function(){ return 'React' })()}</h1> // 函数 // 条件返回 { isGoodWord ? <span>好文章</span> : <span>坏文章</span> } </div> ) } 复制代码
表达式不仅可以插入标签内部,还可以插入属性
render() { const className = 'header' return( <div className={ className }></div> ) } 复制代码
由于
class
是JS的关键字,React.js换成了className
,还有一个就是for
换成了htmlfor
-
-
组件组合
自定义的组件都必须要用大写字母开头
class Title extends Component { render () { return ( <h1>标题标题标题</h1> ) } } class Header extends Component { render () { return ( <div> <!-- 渲染三次 --> <Title /> <Title /> <Title /> </div> ) } } 复制代码
可以直接在
Header
标签里面使用,React.js会在<Title />
,组件的render
方法表示的JSX
内容渲染出来,
它会显示在相应的位置上 -
事件监听
在需要监听事件的元素加上属性类似
onClick
onKeyDown
这样的属性即可,事件属性必须使用驼峰命名法。class Title extends Component { handleOnClickTitle(hello) { console.log(hello) console.log(this) } render () { return ( <!-- 绑定事件时需要绑定this,不绑定则拿不到 --> <h1 onClick={this.handleOnClickTitle.bind(this, 'hello')}>标题标题标题</h1> ) } } 复制代码
在 React.js 不需要手动调用浏览器原生的
addEventListener
进行事件监听。React.js 帮我们封装好了一系列的 on* 的属性,当你需要为某个元素监听某个事件的时候,只需要简单地给它加上 on* 就可以了。而且你不需要考虑不同浏览器兼容性的问题,React.js 都帮我们封装好这些细节了。React.js 将浏览器原生的
event
对象封装了一下,对外提供统一的 API 和属性,这样你就不用考虑不同浏览器的兼容性问题。如果你不手动绑定
this
在函数里面打印的会是undefined
,这是因为 React.js 调用你所传给它的方法的时候,并不是通过对象方法的方式调用,而是直接通过函数调用,所以事件监听函数内并不能通过this 获取到实例如果你想在事件函数当中使用当前的实例,你需要手动地将实例方法 bind 到当前实例上再传入给 React.js
组件的 state 和 setState
state
是组件的状态,我们可以用它来进行状态切换,如显示或隐藏,或者是改变className
等操作。
class Index extends Component {
constructor(props) {
super(props)
<!-- 状态 -->
this.state = {
isShow: true
}
}
render () {
handleState() {
this.setState({
isShow: !this.state.isShow
})
}
return (
<div className='index'>
{
this.state.isShow === true
? <h1>显示<h1/> : null
}
<button onClick={this.handleState.bind(this)}></button>
</div>
)
}
}
复制代码
setState
方法由父类 Component 所提供。当我们调用这个函数的时候,React.js 会更新组件的状态 state ,并且重新调用 render 方法,然后再把 render 方法所渲染的最新的内容显示到页面上。
setState
可以接受函数或者对象作为参数,这里要注意一个问题,当你调用setState
时React并不会马上修改state,而是把这个对象放在一个更新队列,稍后才会从队列当中把新的状态提取出来合并到 state 当中,然后再触发组件更新。
handleState() {
console.log(this.state.isShow) // true
this.setState({
isShow: !this.state.isShow
})
console.log(this.state.isShow) // true
}
复制代码
如果要做到后续操作依赖前一个setState
的这种操作的话,我们可以把参数换成函数。
handleState() {
console.log(this.state.isShow) // true
this.setState((prevState) => {
return { isShow: !this.state.isShow }
})
console.log(this.state.isShow) // false
}
复制代码
在一个函数里面同步使用setState
它只会执行一个,React.js 内部会把js
事件循环中的消息队列的同一个消息中的setState
都进行合并以后再重新渲染组件,如果你先调用一个setState
然后用定时器调用的话,就会触发两次
说到setState
还要说到一个React的受控组件,比如 input
、textarea
这种输入框,如果你直接写这种标签时候React会报一个错误。
state
控制,只要类似于
<input />
、
<textarea />
这样的输入控件被设置了 value 值,那么它们的值永远以被设置的值为准。这时候只要给他绑定一个
onChange
就行了。
class InputWithUserName extends Component {
constructor() {
super()
this.state = {
value: ''
}
}
changeValue(event) {
console.log(event.target.value)
this.setState({
value: event.target.value
})
}
render() {
return (
<div>
{this.props.content}
<input type="text" value={this.state.value} onChange={this.changeValue.bind(this)}/>
</div>
)
}
}
复制代码
Props
每个组件都可以接受一个 props 参数,它是一个对象,包含了所有你对这个组件的配置。
在使用一个组件的时候,可以把参数放在标签的属性当中,所有的属性都会作为 props 对象的键值
class Index extends Component {
render () {
return (
<div className='index'>
<!-- 这里两个花括号只是JSX里面再嵌套一个对象而已 -->
<!-- 还可以传函数 -->
<Main options={{title: 'React', content: 'React大法好'}} onClick={()=> console.log('Click in Index')} />
</div>
)
}
}
class Main extends Component {
<!-- 如果没有东西传过来可以在这里设置默认值 -->
static defaultProps = {
options = {
title: '默认头部',
content: '默认内容'
}
}
<!-- 这里可以省略,因为React会自动生成 -->
constructor(props) {
super(props)
}
handleIndexClick() {
if(this.props.onClick) {
<!-- 执行Index传过来的函数 -->
this.props.onClick()
}
}
render() {
const options = this.props.options
<div className='main'>
<h1>{options.title}</h1>
<p>{options.content}</p>
<button onClick={this.handleIndexClick.bind(this)}></button
</div>
}
}
复制代码
不要试图去改变传过来的props
,React.js 希望一个组件在输入确定的 props 的时候,能够输出确定的 UI 显示形态。如果 props 渲染过程中可以被修改,那么就会导致这个组件显示形态和行为变得不可预测,这样会可能会给组件使用者带来困惑。
handleIndexClick() {
this.props.options = null; // 报错
if(this.props.onClick) {
<!-- 执行Index传过来的函数 -->
this.props.onClick()
}
}
复制代码
但这并不意味着由props
决定的显示形态不能被修改。组件的使用者可以主动地通过重新渲染的方式把新的props
传入组件当中,这样这个组件中由props
决定的显示形态也会得到相应的改变。
class Index extends Component {
constructor (props) {
super(props)
this.state = {
options: {
title: 'React',
content: 'React大法好'
},
}
}
<!-- 修改传过去的值 -->
handleOptionsChange() {
this.setState({
options: {
title: 'Vue',
content: 'Vue大法好'
},
})
}
render () {
return (
<div className='index'>
<Main options={ this.state.options } onClick={()=> console.log('Click in Index')} />
<!-- 点击修改 -->
<button onClick={this.handleOptionsChange.bind(this)}></button>
</div>
)
}
}
复制代码
由于setState
会导致Index
重新渲染,所以<Main />
也会接受到新的值并且重新渲染,这样就能做到修改<Main />
的显示效果
state
和 props
state
的主要作用是用于组件保存、控制、修改自己的可变状态。state
在组件内部初始化,可以被组件自身修改,而外部不能访问也不能修改。你可以认为 state
是一个局部的、只能被组件自身控制的数据源。state
中状态可以通过 this.setState
方法进行更新,setState
会导致组件的重新渲染。
props
的主要作用是让使用该组件的父组件可以传入参数来配置该组件。它是外部传进来的配置参数,组件内部无法控制也无法修改。除非外部组件主动传入新的 props
,否则组件的 props
永远保持不变。
state
和 props
有着千丝万缕的关系。它们都可以决定组件的行为和显示形态。一个组件的 state
中的数据可以通过 props
传给子组件,一个组件可以使用外部传入的 props
来初始化自己的 state
。但是它们的职责其实非常明晰分明:state
是让组件控制自己的状态,props
是让外部对组件自己进行配置。
如果你觉得还是搞不清 state
和 props
的使用场景,那么请记住一个简单的规则:尽量少地用 state
,尽量多地用 props
。没有 state
的组件叫 无状态组件(stateless component),设置了 state
的叫做 有状态组件件(stateful component)。因为状态会带来管理的复杂性,我们尽量多地写无状态组件,尽量少地写有状态的组件。这样会降低代码维护的难度,也会在一定程度上增强组件的可复用性。前端应用状态管理是一个复杂的问题。
有状态组件
class HelloWorld extends Component {
constructor() {
super()
this.state = {
isShow: true
}
}
sayHi () {
alert('Hello World')
}
render () {
return (
<div onClick={this.sayHi.bind(this)}>Hello World</div>
)
}
}
复制代码
无状态组件
const HelloWorld = (props) => {
const sayHi = (event) => alert('Hello World')
return (
<div onClick={sayHi}>Hello World</div>
)
}
复制代码
函数式的组件编写方式是一个函数就是一个组件,你可以和以前一样通过<HellWorld />
使用该组件。不同的是,函数式组件只能接受 props
而无法像跟类组件一样可以在 constructor 里面初始化 state
。你可以理解函数式组件就是一种只能接受 props
和提供render
方法的类组件。
渲染列表数据
直接使用ES6的map遍历代码比较简洁,也可以手动写循环构建列表的JSX,在React遍历使用map是非常常见的。
const users = [
{ username: 'Jerry', age: 21, gender: 'male' },
{ username: 'Tomy', age: 22, gender: 'male' },
{ username: 'Lily', age: 19, gender: 'female' },
{ username: 'Lucy', age: 20, gender: 'female' }
]
class Index extends Component {
render () {
return (
<div>
{users.map((user, i) => <User user={user} key={i} />)}
</div>
)
}
}
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>
)
}
}
复制代码
React.js 的是非常高效的,它高效依赖于所谓的 Virtual-DOM 策略。简单来说,能复用的话 React.js 就会尽量复用,没有必要的话绝对不碰 DOM。对于列表元素来说也是这样,但是处理列表元素的复用性会有一个问题:元素可能会在一个列表中改变位置。但其实 React.js 只需要交换一下 DOM 位置就行了,但是它并不知道其实我们只是改变了元素的位置,所以它会重新渲染后面两个元素(再执行 Virtual-DOM 策略),这样会大大增加 DOM 操作。但如果给每个元素加上唯一的标识,React.js 就可以知道这两个元素只是交换了位置
render () {
return (
<div>
{users.map((user, i) => <User user={user} key={i} />)}
</div>
)
}
复制代码
这样 React.js 就简单的通过 key 来判断出来,这两个列表元素只是交换了位置,可以尽量复用元素内部的结构。
对于用表达式套数组罗列到页面上的元素,都要为每个元素加上 key 属性,这个 key 必须是每个元素唯一的标识,一般后台会返回。