1、React的基本概念
1、单页模型(spa):新的网页开发模型,就是说当要浏览不同的网页时,客户端不用去频繁的想服务器发起请求,只需要将新页面的数据拉取下来,根据客户端的具体情况在本地重新绘制新的界面,将新的数据展现在本地构建的新的页面。整个过程减少了客户端和服务端的交互延迟,能够及时的响应。
2、单页模型的困境:
(1)如何保持数据和UI同步的更新,就是新的数据和新的本地界面能同时的更新。
(2)如何提高DOM操作的效率。
(3)html开发UI界面异常的复杂
3、React的及时登场(React的好处)
(1)自动化的UI管理(使得界面和数据能够保持同步,数据的变换转化为事件,开发者只需要根据事件去改变界面的状态,简单的说整个流程就是:数据的变化->事件的发生->界面的更新)
(2)高效的DOM操作效率
在内存中存在Virtual DOM的结构,对DOM的操作转化为对虚拟DOM的操作,提高了效率。
(3)UI的组件化设计,可重用的组件和简单的组件能够组合成为复杂的组件,使用JSX减少对CSS的样式依赖。
4、总结:React实际上是MVC架构中的V,就是view部分,由于UI的组件化设计、自动化的UI管理、高效的DOM操作效率这单个特性,使得React大幅度减少了数据和页面交互的难度(这句话必须要记住)
5、react fiber指的是React 16这个大版本。
2、开发环境的搭建
1、使用脚手架工具,在 脚手架中写的代码并不能直接运行,需要靠工具来进行编译,编译后的代码才能被浏览器进行识别。关于脚手架工具有GRUNT,gulp,webpack,这些脚手架工具不必自己实现,会用就可以,但是关于脚手架工具的编写是可以单独作为一个系列的课程来学的。
2、关于脚手架的工具建议使用官方的Create-react-app
3、在有node的环境前提下(在命令行中可以使用node -v 和npm -v来查看自己node和npm的版本),使用命令npm install -g create-react-app,来下载官方脚手架工具。
4、在window操作系统下,将shift和鼠标右键同时按下,点击在此处打开命令窗口:
(1)使用命令creat-react-app todolist来在桌面上创建一个todolist的文件夹。
(2)然后在命令行中cd todolist 。
(3)输入yarn start运行项目(有的是npm start)。
(4)会自动打开浏览器显示这样一个网页,网址是localhost:3000,网页内容如下:
到这里为止我们就用官方的脚手架创建了一个react的项目。
3、项目文件的结构
(1)图中的public中的favicon.ico是我们网页最上面每一个导航条里面最左边的小图标。
(2)index.html就是我们的首页,其中的内容很简单,如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<title>React App</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
</body>
</html>
(3)那么src是最重要的一个目录,这里放着我们所有的源代码,所有源代码的入口就是这个index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker();
前面都是一些模块的引入,特别是这个App文件,没有后缀,但是项目会自动去先找App.js这个文件,也就是会自动补全后缀。那么下面正文就不用多说,将<App>这个视图插入到root这个DOM结点中,root这个容器在哪里,就在public文件夹中index.html文件中,那么空的容器,它的id叫做root。
还有registerServiceWorker是什么东西,这里就有个很重要的概念,pwa,progrcessive web application,就是使用写网页的方式去写一些手机app ,那么这个有啥功能,除了帮助写手机app,假如我们这个网页上线到一个https的服务器上,我们的网页就有这样的特性,第一次访问需要联网,第二次断网的时候,也能访问到一样的页面,因为 registerServiceWorker帮助存储在了浏览器当中。
(4)那么有了pwa的概念后我们回头去看看manifest.json里面的内容
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": "./index.html",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
我们有了缓存,我们在桌面上和手机上就可以创建快捷方式,直接进入这个网址,快捷方式的图标就是icons来定义的,快捷方式的跳转网址就是start_url定义的,还有主题颜色和背景颜色等等。
(5)下面来看看这个App是什么内容:
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>
</div>
);
}
}
export default App;
其中这个logo就是我们那个页面中旋转的react 的logo,可以看到return内容中的很多标签都有className,它们用来记录这个标签,能在App.css文件中定义样式。
4、组件基础
1、组件化的思想使得复杂的页面简单化。
2、一个类继承component,它就变成了一个组件。其中render函数中return什么,组件就显示什么。
3、ReactDOM.render(<App />, document.getElementById('root')) 这个方法帮我们将组件挂载到某个DOM节点上,这句话就是将App这个组件挂载到id=root的这个结点之下。特别注意的是:我们这句话中使用了<App/ >这样的JSX的语法,那么我们这个文件就必须要将React引入进来,即import React from 'react' 这句话不能少,否则就不能编译通过。
4、还有一种情况,如下图
export default class App extends Component{
render(){
return (
<div/>hello</div>
)
}
}
表面上看起来div是html的标签,实际上在react中,这些标签都是JSX的语法,也就是说都是组件,react组件分为自定义组件和原生组件,上面我们说的App就是个自定义的组件,而div就是原生的组件。使用JSX语法就一定要引入React。即import React from 'react' 这句话不能少。
5、JSX是什么
1、在js中使用像html的标签的写法就是JSX,但是记住,如果是自己定义的组件,首字母要大写,例如App组件,不能写成app组件,JSX语法是不支持的。
2、有一种简单的判断方法,一般<>中组件的首字母大写,就是自定义组件,小写就是原生的组件。
6、Fragment
1、在JSX语言当中我们render函数只允许去放回一个试图组件,也就是说当我们把代码写成下面这样是不行的:
render(){
return (
<div style={{marginTop:10}}>
<input/><button>提交</button>
</div>
<ul>
<li>学英语</li>
<li>learn react</li>
</ul>
)
}
因为return中返回了两个视图组件,所以我们要在俩个视图组件外面去包裹一个div,但是有些时候我们就像把两个组件分开,不想让它们在一个div中,那么我们可以使用占位符Fragment,这样在浏览器上就不会报错:(注意引入Fragment)
import React,{Component,Fragment} from 'react';
export default class Todolist extends Component{
render(){
return (
<Fragment>
<div style={{marginTop:10}}>
<input/><button>提交</button>
</div>
<ul>
<li>学英语</li>
<li>learn react</li>
</ul>
</Fragment>
)
}
}
7、React中的响应式思想和事件绑定(重要)
1、响应式的思想是这样的,我们不用去操作DOM,去操作数据,因为React会自动感知数据的变化,自动的帮你生成DOM,所以我们不必去关注DOM,去关注数据。
2、我们在写一个类的构造函数有一种固定的写法:
constructor(props){
super(props);
}
因为我们自定义的类去继承component,那么在写自己的构造函数的时候要调用一次父类的构造函数,super就是这个功能。
3、在JSX语法中,要使用JS的变量和表达式,要在外部用大括号包起来。例如下面
<input value={this.state.value}/><button>提交</button>
this.state.value是JS的变量,写在JSX中使用{}将其包裹起来。
4、React在英文当中的意思是响应,假如页面的组件的属性和数据进行绑定,那么数据的变化会自动导致页面的属性变化,从而导致页面的变化。
5、this+bind、箭头函数两者形成的三种写法
(1)onChange={this.handleChangeInput} handleChangeInput=(event)=>{}
handleChangeInput=(event)=>{
this.setState({
inputValue:event.target.value
})
}
render(){
return (
<Fragment>
<div style={{marginTop:10}}>
<input
value={this.state.inputValue}
onChange={this.handleChangeInput}
/>
<button>提交</button>
</div>
</Fragment>
)
}
(2)onChange={this.handleChangeInput.bind(this)} handleChangeInput(event){}
handleChangeInput(event){
this.setState({
inputValue:event.target.value
})
}
render(){
return (
<Fragment>
<div style={{marginTop:10}}>
<input
value={this.state.inputValue}
onChange={this.handleChangeInput.bind(this)}
/>
<button>提交</button>
</div>
</Fragment>
)
}
(3)onChange={(event)=>this.handleChangeInput(event)} handleChangeInput(event){}
handleChangeInput(event){
this.setState({
inputValue:event.target.value
})
}
render(){
return (
<Fragment>
<div style={{marginTop:10}}>
<input
value={this.state.inputValue}
onChange={(event)=>this.handleChangeInput(event)}
/>
<button>提交</button>
</div>
</Fragment>
)
}
6、数据驱动的过程:通过下面这个输入框中输入内容的程序来深度讲解
import React,{Component,Fragment} from 'react';
export default class Todolist extends Component{
constructor(props){
super(props);
this.state={
inputValue:'',
list:[],
}
}
handleChangeInput(event){
this.setState({
inputValue:event.target.value
})
}
render(){
return (
<Fragment>
<div style={{marginTop:10}}>
<input
value={this.state.inputValue}
onChange={(event)=>this.handleChangeInput(event)}
/>
<button>提交</button>
</div>
</Fragment>
)
}
}
(1)首先 inputValue:''表示:inputValue的初始状态为空
(2)value={this.state.inputValue} 表示:输入框中的内容是inputValue
(3)你在输入框中输入字母的时候实际上发生了一个事件event
(4)在这个事件event中记录了你的输入event.target.value
(5)我们将输入框中的内容inputValue改变成event事件里已记录的你刚输入的字母
(6)数据inputValue变化,通过value={this.state.inputValue}来告知了输入框,你显示的内容该变了
7、展开运算符
this.setState({
list:[...this.state.list,this.state.inputValue]
})
这个是ES6中的写法,就是重新构建个数组,这个数组是由之前的数组里面所有的旧内容和新的内容this.state.inputValue共同组成,这里就用...this.state.list将之前数组中所有的旧内容全部展开放在这里,和this.state.inputValue共同成为这个新数组中的所有成员。
8、immutable
(1)当我们要去改变state中的内容,要使用this.setState({})这个方法,不能去直接操作state中的内容。
(2)像数组中的操作,我们要拷贝一份state中的副本,去修改副本,然后将副本通过this.setState的方法传给数组。下面这种写法就是错误的:
this.state.list.splice(index,1)
this.setState({
list:this.state.list
})
正确的写法这样:
let list=[...this.state.list]; //拷取state数组的副本
list.splice(index,1); //修改副本
this.setState({
list:list, //副本传回值
})
8、其他JSX语法补充
1、借助脚手架的能力,在js文件当中可以去引入css文件。可以直接去引入这个css文件。
import './style.css';
使用的时候采用组件属性的calssName=‘input’,input在css文件中是个选择器,也可以叫做类:
.input{
border:1px solid red
}
2、
(1)在JSX中显示一些内容,但这些内容我们不希望转义一些内容,比如我们在input输入框中输入<h1>hello world</h1>,它会自动转义,显示原原本本的<h1>hello world</h1>字符串,那么我们不希望转义的时候使用dangerouslySetInnerHTML这个属性。
(2)所以dangerouslySetInnerHTML的功能就是使我们某些标签里的内容不被转义。
比如我们原来的代码是
<li
key={index}
onClick={(index)=>this.handleItemDelete(index)}
>{item}</li>
使用了dangerouslySetInnerHTML这个属性之后
<li
key={index}
onClick={(index)=>this.handleItemDelete(index)}
dangerouslySetInnerHTML={{__html:item}}
></li>
特别注意的是{{}}的含义,外层的{}表示这里面是个JS的表达式,里面的{}表示这是个对象。其中item就是我们设置不转义的内容。
3、label的使用
(1)在html中我们对label的定义是:
<label> 标签为 input 元素定义标注(标记)。
label 元素不会向用户呈现任何特殊效果。不过,它为鼠标用户改进了可用性。如果您在 label 元素内点击文本,就会触发此控件。就是说,当用户选择该标签时,浏览器就会自动将焦点转到和标签相关的表单控件上。
<label> 标签的 for 属性应当与相关元素的 id 属性相同。
(2)在react中,我们就不能用for来作为这个属性的名字,因为for是循环的代表,所以我们使用htmlFor来代替这个属性
<label htmlFor="insertAre">点击这里自动将焦点转到与标签相关的表单控件上</label>
input
id="insertAre"
/>
9、组件的传值
1、父组件向子组件传递值,通过属性的方法。子组件通过this.props.xxx来接受这个数据。
return(
<Todoitem
key={index}
content={item}
index={index}
handleItemDelete={(index)=>this.handleItemDelete(index)}
/>
)
2、子组件向父组件传值,通过调用父组件的方法来修改父组件的内容。
import React,{Component} from 'react';
export default class Todoitem extends Component{
constructor(props){
super(props)
this.handleClick=this.handleClick.bind(this)
}
render(){
return<div onClick={this.handleClick}>{this.props.content}</div>
}
handleClick(){
this.props.handleItemDelete(this.props.index)
}
}
3、当父组件向子组件中传递很多值,在子组件中取值都要用到this.props.xxx,这样很长很麻烦,使用解构的写法,在使用属性值直接可以用xxx来代替this.props.xxx。简化代码的书写。
const {key,content,index,handleItemDelete}=this.props;
10、代码的优化
1、在新版的react中我们的setState已经不推荐使用老的写法,如下
handleChangeInput(event){
this.setState({
inputValue:event.target.value
})
}
我们里面的对象可以换成函数
handleChangeInput(event){
let value=event.target.value;
this.setState(()=>{
return {
inputValue:value
}
})
}
在新版的语法中我们都可以使用小括号去代替return的对象,这个是ES6对return的简写
handleChangeInput(event){
let value=event.target.value; //变更数据event.target.value存在外层
this.setState(()=>({
inputValue:value //内部使用变量value
}))
}
4、当我们使用的上面的setState中用箭头函数返回对象这种方式,就要注意它是一种异步的写法, 最好将变更的数据存在外层,在内层使用变量就行了。
5、展开运算符的进阶写法,使用prevState去代替this.state
handleBtnClick(){
this.setState((prevState)=>({
list:[...prevState.list,prevState.inputValue],
inputValue:'',
}))
}
6、当我们存在return{list:list}这种写法的时候我们后面的list可以省略,而且对变量list的操作拉进setState中
//新式写法
handleItemDelete(index){
this.setState((prevState)=>{
let list=[...prevState.list];
list.splice(index,1);
return{list}
})
}
//旧时写法
handleItemDelete(index){
let list=[...this.state.list];
list.splice(index,1);
this.setState({
list:list,
})
}
11、由react衍生出的思考
1、声明式的开发
react的开发和jquery直接操作DOM的方式不同,不属于命令式的编程,以数据为主
2、可以和其它框架并存,因为下面的这个句话
ReactDOM.render(<Todolist />, document.getElementById('root'));
registerServiceWorker();
整个Todolist的内容只挂载在rootDOM结点中,只影响root这个结点中的DOM结构。而html还有其它的div,其它div可以用其它框架去操作DOM,只要不影响root的DOM结构。
3、单项数据流
父组件可以去给子组件传值,但是这个值到了子组件中只有读的属性,没有写的属性,就是说子组件只能读取不能修改。原因是父组件中如果含有很多的子组件,每个子组件都能修改这个值,那其他组件的内容都会发生变化。而且出了问题不好定位。
4、React只是视图层的框架
由于父子传值的局限性,在不同的两个组件之间必须找到某种复杂的父子关系才能通信。大型项目忍不了!任何两个组件的通信都会有其他组件作为桥梁参与进来。所以React是负责页面和数据渲染的问题,React经常搭配其他数据工具来共同工作。比如FLUX,REDUX,MOBUX。
5、函数式编程
维护方便,前端自动化测试容易进行,给函数一个值看输出是否正确。