在阅读以前,会有一些小伙伴以为疑惑,做者怎么写前端文章了呢,做者不是专一后端Java吗?这是怎么了?其实否则,在4年前,那个时候尚未流行vue和react,身为后端程序员的咱们,无论是java仍是php程序员,都是须要写前端的,不过那个时候的前端没有如今那么多东西。咱们通常叫美工画好静态页面,而后交给后端程序员,后端程序员在静态页面中加入js代码,或者把静态页面替换成jsp、velocity等静态模板语言的代码,一个动态效果的页面就完成了。随着互联网的不断发展,程序员工种的划分也愈来愈明细,如今的前端和做者曾经那个时候已经大不同了,不管是思想仍是语言风格,为了学习下如何本身制做页面,也为了感觉下前端代码的魅力,故选择了React.js 前端框架做为学习目标。其实前端颇有趣,有意思!身为后端程序员的你不打算了解一下嘛~php
在学习 react 以前,咱们须要先安装对应的运行环境,工欲善其事必先利其器。首先安装好以下环境:css
不知道个人读者是否是彻底不懂前端,建议读者有一点点的 Html、Css、java script、es6 的基础,实在没有建议花个1~2天学习下。html
react 前端项目和咱们平时的java项目同样,都有其本身的项目结构,java的项目结构有IDE开发工具帮咱们生产,在本文中,咱们使用facebook 的 create-react-app 脚手架项目来帮咱们生成 react 项目结构,操做以下:前端
# 全局安装官方脚手架
npm i -g create-react-app
# 初始化建立一个基于 react 的项目
create-react-app 01_jpview_class
# 设置 npm 下载镜像源为淘宝, 和设置 maven 仓库源一个意思
npm config set registry http://registry.npm.taobao.org
复制代码
![]()
├── README.md 文档 ├── package-lock.json ├── package.json npm 依赖 ├── public 静态资源 │ ├── favicon.ico │ ├── index.html │ └── manifest.json └── src 源码 ├── App.css ├── App.js 根组件 ├── App.test.js 测试 ├── index.css 全局样式 ├── index.js 入口 ├── logo.svg └── serviceWorker.js pwa支持 复制代码
删除src目录下的因此文件,新建一个 index.js文件,内容为vue
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App/>, document.querySelector('#root'))
复制代码
新建 App.js文件,内容为:java
import React, { Component } from "react";
export default class App extends Component{
render(){
return <div>
<button>雷猴啊</button>
</div>
}
}
export default KaikebaCart
复制代码
上面的代码看起来会有感到困惑的地方,首先就是ReactDOM.render(<App />, document.querySelector('#root'));
看起来是js和html的混合体,这种语法被称之为JSX,其实际核心的逻辑彻底是js来实现的。node
在项目目录终端执行如下命令能够看到效果react
# 下载依赖包
npm install
# 启动运行项目
npm start
复制代码
如下全部代码均可以直接复制到 index.js
文件中 体验效果git
React
的世界里一切皆是组件,咱们使用class
语法构建一个最基本的组件,组件的使用方式和HTML相同,组件的render
函数返回渲染的一个JSX页面,而后使用ReactDom渲染到页面里程序员
import React from 'react';
import ReactDOM from 'react-dom';
// 继承React.Component表示App是一个组件
class App extends React.Component {
render() {
return <div> Hello React </div>
}
}
// ReactDOM.render()方法把 App中的内容追加到 index.html 中 <div id="root">的标签上
ReactDOM.render(<App/>, document.querySelector('#root'))
复制代码
React
组件使用和html
相似的方式传递参数,在组件内部,使用this.props
获取全部的传递的参数,在JSX
里使用变量,使用{}
包裹
import React from 'react';
import ReactDOM from 'react-dom';
// 继承React.Component表示App是一个组件
class App extends React.Component {
render() {
// 获取<App name="React"> 传递过来的属性name值
return <div> Hello {this.props.name} </div>
}
}
// ReactDOM.render()方法把 App中的内容追加到 index.html 中 <div id="root">的标签上
ReactDOM.render(<App name="React" />, document.querySelector('#root'))
复制代码
JSX
是一种js的语法扩展,表面上像HTML,本质上仍是经过babel
转换为js执行,全部在JSX
里可使用{}
来写js的语法,JSX
本质上就是转换为React.createElement
在React内部构建虚拟Dom
,最终渲染出页面
import React from 'react';
import ReactDOM from 'react-dom';
// 继承React.Component表示App是一个组件
class App extends React.Component {
render() {
return (
<div>
// {2+2} js的计算语法,结果为4
Hello {this.props.name}, I am {2 + 2} years old
</div>
)
}
}
// ReactDOM.render()方法把 App中的内容追加到 index.html 中 <div id="root">的标签上
ReactDOM.render(<App name="React" />, document.querySelector('#root'))
复制代码
咱们到如今为止尚未更新过UI页面,React
内部经过this.state
变量来维护内部的状态,而且经过this.stateState
来修改状态,render
里用到的state
变量,也会自动渲染到UI,咱们如今constructor()
来初始化state
,在JSX
语法里使用this.state.num
获取,而后jsx
里使用onClick
绑定点击事件,注意这里须要在constructor()
里使用bind()方法
绑定this
指向,而后内部调用this.setState
修改值,注意这里不能写成this.state.num+1,而是要调用this.setState
,设置并返回一个全新的num值。
import React from 'react';
import ReactDOM from 'react-dom';
class Counter extends React.Component {
constructor(props){
super(props)
// 初始化构造是设置内部状态 num值为 1
this.state = {
num:1
}
// 把handleClick()方法绑定到当前对象Counter上
this.handleClick = this.handleClick.bind(this)
}
handleClick(){
// 改变内部状态 num 的值
this.setState({
num:this.state.num + 1
})
}
render() {
return (
<div>
<p>{this.state.num}</p>
{/*{this.handleClick} js语法调用当前对象的handleClick()方法*/}
<button onClick={this.handleClick}>click</button>
</div>
)
}
}
ReactDOM.render(<Counter/>, document.querySelector('#root'))
复制代码
在组件内部存在一些特殊的方法,会在组件的不一样阶段执行,好比组件加载完毕后会执行componentDidMount
函数,组件更新的时候,会执行shouldComponentUpdate
函数,若是返回true
的话,就会一次执行componentWillMount
、render
,componentDidMount
,若是返回false
的话,就不会执行。
import React from 'react';
import ReactDOM from 'react-dom';
class Counter extends React.Component {
constructor(props){
super(props)
this.state = {
num:1
}
this.handleClick = this.handleClick.bind(this)
}
// 生命方法--组件渲染完成,只执行一次
componentDidMount(){
console.log('componentDidMount 函数触发')
}
// 生命方法--避免组件重复或者无心义渲染
shouldComponentUpdate(nextProps,nextState){
if (nextState.num%2) {
return true
}
return false
}
handleClick(){
this.setState({
num:this.state.num+1
})
}
render() {
return (
<div>
<p>{this.state.num}</p>
<button onClick={this.handleClick}>click</button>
</div>
)
}
}
ReactDOM.render(<Counter/>, document.querySelector('#root'))
复制代码
用户想提交数据到后台,表单元素是最经常使用的,一个常见的表单由form
、 input
、 label
等标签构成,咱们经过onChange()方法
控制value
的值,最终经过state
,让在的html input
中输入内容和`React``关联起来。
import React from 'react';
import ReactDOM from 'react-dom';
class TodoList extends React.Component {
constructor(props){
super(props)
this.state = {
text:''
}
this.handleClick = this.handleClick.bind(this)
this.handleChange = this.handleChange.bind(this)
}
handleClick(){
// 若是内部状态 text有值,则把值清空''
if (this.state.text) {
this.setState({
text:''
})
}
}
handleChange(e){
// 获取事件元素input的值赋值给内部状态 text 中
this.setState({
text:e.target.value
})
}
render() {
return (
<div>
{/* 显示内部状态 text 的内容*/}
{this.state.text}
{/*input接收到输入值调用handleChange()方法*/}
<input type="text" value={this.state.text} onChange={this.handleChange}/>
{/*点击按钮调用handleClick()方法*/}
<button onClick={this.handleClick}>clear</button>
</div>
)
}
}
ReactDOM.render(<TodoList/>, document.querySelector('#root'))
复制代码
页面里序列化的数据,好比用户列表,都是一个数组,咱们经过map
函数把数组直接映射为JSX
,可是咱们直接渲染列表,打开console的时候会看到Each child in an array or iterator should have a unique "key" prop.
报错。在渲染列表的时候,咱们须要每一个元素都有一个惟一的key属性,这样React
在数据变化的时候,知道哪些dom
应该发生变化 尤为注意key
要惟一,建议每一个字段惟一id,或者使用索引
import React from 'react';
import ReactDOM from 'react-dom';
class TodoList extends React.Component {
constructor(props){
super(props)
// 内部装填属性初始化值
this.state = {
todos:['Learn React','Learn Ant-design','Learn Koa'],
text:''
}
this.handleClick = this.handleClick.bind(this)
this.handleChange = this.handleChange.bind(this)
}
handleClick(){
if (this.state.text) {
this.setState(state=>({
// 若是内部状态 text有值,追加到解构的todos数组后
todos:[...state.todos,state.text],
// 若是内部状态 text有值,则把值清空''
text:''
}))
}
}
handleChange(e){
// 获取事件元素input的值赋值给内部状态 text 中
this.setState({
text:e.target.value
})
}
render() {
return (
<div>
{/*input接收到输入值调用handleChange()方法*/}
<input type="text" value={this.state.text} onChange={this.handleChange}/>
{/*点击按钮调用handleClick()方法*/}
<button onClick={this.handleClick}>add</button>
<ul>
{/*map()循环输出JSX内容给ReactDOM*/}
{this.state.todos.map(v=>{
return <li key={v}>{v}</li>
})}
</ul>
</div>
)
}
}
ReactDOM.render(<TodoList/>, document.querySelector('#root'))
复制代码
2017年9月27日,Facebook 官方发布了 React 16.0。相较于以前的 15.x 版本,v16是第一个核心模块重写了的版本,而且在异常处理,核心架构和服务端渲染方面都有更新。
render
函数支持返回数组和字符串componentDidCatch
钩子获取组件错误portals
能够渲染当前容器dom
以外的节点import React from 'react';
import ReactDOM from 'react-dom';
// 继承React.Component表示React16是一个组件
class React16 extends React.Component {
// 构造器函数
constructor(props){
super(props)
this.state={hasError:false}
}
// 生命周期函数
componentDidCatch(error, info) {
// 设置内部状态 hasError为true
this.setState({ hasError: true })
}
render() {
return (
<div>
{/*? : 是三目运算符*/}
{this.state.hasError ? <div>出错了</div>:null}
{/*使用组件ClickWithError和FeatureReturnFragments*/}
<ClickWithError />
<FeatureReturnFragments />
</div>
)
}
}
// 继承React.Component表示ClickWithError是一个组件
class ClickWithError extends React.Component{
constructor(props){
super(props)
this.state = {error:false}
// 绑定handleClick()方法到当前对象上
this.handleClick = this.handleClick.bind(this)
}
handleClick(){
// 触发调用时设置state.error值为true
this.setState({
error:true
})
}
render() {
if (this.state.error) {
throw new Error('出错了!')
}
return <button onClick={this.handleClick}>抛出错误</button>
}
}
// 继承React.Component表示FeatureReturnFragments是一个组件
class FeatureReturnFragments extends React.Component{
render(){
return [
<p key="key1">React很不错</p>,
"文本1",
<p key="key2">Antd-desing也很赞</p>,
"文本2"
]
}
}
ReactDOM.render(<React16/>, document.querySelector('#root'))
复制代码
DOM操做成本实在是过高,因此有了在js里模拟和对比文档对象模型的方案,JSX里使用 react
、 createElement
构建虚拟DOM
,每次只要有修改,先对比js里面的虚拟dom树
里的内容。 传统浏览器渲染流程图
学完了api 的使用,是时候拿起武器开始真刀真枪的开干了,如图是实战的效果演示,具体的代码分析讲解能够直接在个人github上看到,就不在本文赘述了,我要传送代码仓库===> 项目代码地址
以下代码中类的构造方法constructor()
,Test
类继承了react Component
这个基类,也就继承这个react
的 基类,才能有render()
,生命周期等方法
可使用,这也说明为何函数组件不能使用这些方法的缘由。
super(props)
用来调用基类的构造方法constructor()
, 也将父组件的props
注入给子组件,供子组件读取(组件 中props
属性只读不可写,state
可写)。 而 constructor()
用来作一些组件的初始化工做,好比定义this.state
的初始内 容。
import React, { Component } from 'react';
class Test extends Component {
constructor(props) {
super(props);
}
}
复制代码
此阶段分为componentWillMount
,render
,componentDidMount
三个时期。
DOM
前调用,且只会被调用一次,在这里面调用this.setState
不会引发组件的从新渲染,也能够把写在这里面的内容改写到constructor()
中,因此在项目中不多这么使用。props
和state
(不管二者是重传递或重赋值,不管值是否有变化,均可以引发组件从新render
) ,内部return
一个React
元素(描述组件,即UI),该元素不负责组件的实际渲染工做,以后由React
自身根据此元素去渲染出页面DOM。render
是纯函数 (Pure function:函数的返回结果只依赖于它的参数;函数执行过程里面没有反作用)
,不能在里面执行this.setState
等操做,会有改变组件状态的反作用。DOM
后调用,且只会被调用一次在组件的更新阶段中,存在不少生命方法,从上图能够很直观的看到,有 componentWillReceiveProps
, shouldComponentUpdate
,componentWillUpdate
,render
,componentDidUpdate
。
- componentWillReceiveProps(nextProps):此方法只调用于
props
引发的组件更新过程当中,参数nextProps
是父组件传给当前组件的新props
。但父组件render
方法的调用不能保证重传给当前组件的props
是有变化的,因此在此方法中根据nextProps
和this.props
来查明重传 的props
是否改变,以及若是改变了要执行啥,好比根据新的props
调用this.setState
出发当前组件的从新render
- shouldComponentUpdate(nextProps,nextState):此方法经过比较
nextProps
,nextState
及当前组件的this.props
,this.state
,返回true时当前组件将继续执行更新过程,返回false则当前组件更新中止,以此可用来减小组件的没必要要渲染,优化组件性能。 这边也能够看出,就算componentWillReceiveProps()
中执行了this.setState
,更新了state
,但在render
前 (如shouldComponentUpdate
,componentWillUpdate
),this.state
依然指向更新前的state
,否则nextState
及当前组件的this.state
的对比就一直是true了。- componentWillUpdate(nextProps, nextState):此方法在调用
render
方法前执行,在这边可执行一些组件更新发生前的工做,通常较少用。- render:
render
方法在上文讲过,这边只是从新调用。- componentDidUpdate(prevProps, prevState):此方法在组件更新后被调用,能够操做组件更新的
DOM
,prevProps
和prevState
这两个参数指的是组件更新前的props
和state
在此阶段须要先明确下react
组件更新机制。setState
引发的state
更新,或父组件从新render
引发的props
更新,更新后的state
和props
相比较以前的结果,不管是否有变化,都将引发子组件的从新render
。详细了解可看=>这篇文章 形成组件更新有两类(三种)状况:
render
引发子组件从新render
的状况有两种
直接使用,每当父组件从新
render
致使的重传props
,子组件都将直接跟着从新渲染,不管props
是否有变化。可通 过shouldComponentUpdate
方法控制优化。class Child extends Component { // 应该使用这个方法,不然不管props是否有变化都将会致使组件跟着从新渲染 shouldComponentUpdate(nextProps){ if(nextProps.someThings === this.props.someThings){ return false } } render() { return <div>{this.props.someThings}</div> } } 复制代码
componentWillReceiveProps
方法中,将props
转换成本身的state
class Child extends Component { constructor(props) { super(props); this.state = { someThings: props.someThings }; } componentWillReceiveProps(nextProps) { // 父组件重传props时就会调用这个方法 this.setState({someThings: nextProps.someThings}); } render() { return <div>{this.state.someThings}</div> } } 复制代码
根据官网的描述: 在
componentWillReceiveProps
方法中,将props
转换成本身的state
是由于componentWillReceiveProps
中判断props
是否变化了,若变化了,this.setState
将引发state
变化,从而引 起render
,此时就不必再作第二次因重传props
来引发render
了,否则就重复作同样的渲染了。
setState
,不管state
有没有变化。能够经过shouldComponentUpdate
方法控制优化。
shouldComponentUpdate() {
// 组件是否须要更新,返回布尔值,优化点
console.log("5.组件是否应该更新?");
return true;
}
复制代码
此阶段只有一个生命周期方法:componentWillUnmount
此方法在组件被卸载前调用,能够在这里执行一些清理工做,好比清楚组件中使用的定时器,清除componentDidMount
中手动建立的DOM元素
等,以免引发内存泄漏。
Fiber
以后就不合适了,由于若是要开启
async rendering
, 在render函数以前的全部函数,都有可能被执行屡次。
原来(React v16.0前)的生命周期有哪些是在render前执行的呢?
若是开发者开了async rendering
,并且又在以上这些render
前执行的生命周期方法作AJAX请求的话,那AJAX将被 无谓地屡次调用。。。明显不是咱们指望的结果。并且在componentWillMount
里发起AJAX,无论多快获得结果 也赶不上首次render
,并且componentWillMount
在服务器端渲染也会被调用到(固然,也许这是预期的结 果),这样的IO操做放在componentDidMount
里更合适。
禁止不能用比劝导开发者不要这样用的效果更好,因此除了shouldComponentUpdate
,其余在render
函数以前的 全部函数(componentWillMount
,componentWillReceiveProps
,componentWillUpdate
)都被 getDerivedStateFromProp
s替代。
也就是用一个静态函数getDerivedStateFromProps
来取代被deprecate的几个生命周期函数,就是强制开发者在 render
以前只作无反作用的操做,并且能作的操做局限在根据props
和state
决定新的state
React v16.0刚推出的时候,是增长了一个componentDidCatch
生命周期函数,这只是一个增量式修改,彻底不影 响原有生命周期函数;可是,到了React v16.3,大改动来了,引入了两个新的生命周期函数。
getDerivedStateFromProps
原本(React v16.3中)是只在建立和更新(由父组件引起部分),也就是否是不禁 父组件引起,那么getDerivedStateFromProps
也不会被调用,如自身setState
引起或者forceUpdate
引起。这样的话理解起来有点乱,在React v16.4中改正了这一点,让getDerivedStateFromProps
不管是Mounting
仍是Updating
,也不管是由于什么引发的Updating
,所有都会被调用,具体可看React v16.4 的生命周期图。
static getDerivedStateFromProps(props, state)
在组件建立时和更新时的render
方法以前调用,它应该返回 一个对象来更新状态,或者返回null来不更新任何内容。
getSnapshotBeforeUpdate()
被调用于render
以后,能够读取但没法使用DOM
的时候。它使您的组件能够在可 能更改以前从DOM
捕获一些信息(例如滚动位置)。今生命周期返回的任何值都将做为参数传递给 componentDidUpdate()。
class ScrollingList extends React.Component {
constructor(props) {
super(props);
this.listRef = React.createRef();
}
getSnapshotBeforeUpdate(prevProps, prevState) {
//咱们是否要添加新的 items 到列表?
// 捕捉滚动位置,以便咱们能够稍后调整滚动.
if (prevProps.list.length < this.props.list.length) {
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;
}
}
render() {
return (
<div ref={this.listRef}>{/* ...contents... */}</div>
);
}
}
复制代码