第一部分:react
一、react开发环境准备
A. 通过脚手架工具来编码
Creact-react-app官方提供的脚手架工具node -v查看node版本号npm -v查看nom版本号
B. 安装
npm i creat-react-app -g在全局安装依赖creat-react-app todolist创建todolist文件cd todolist基于creat-react-app构建的todolist项目目录npm start启动开发服务器。npm run build将应用程序打包成静态文件进行生产。npm test启动测试运行程序。npm startNPM启动
在浏览器输入http://localhost:3000/,出现react欢迎页即项目启动成功
二、项目目录分析
- node-modules node包文件
- public
- favicon.icon url 图标
- index.html 项目首页
- manifest.json
- src
- index.js 整个程序运行的入口文件 引入App文件
- App.js 页面内容
- gitgnore 上传git时,如果有文件不想传到git上,可以放在这个文件下
- package-lock.json 项目文件依赖
- package.json 项目介绍 node包文件
- README.md 项目文件说明
在index.js文件中有一行代码
// PWA 手机App // https协议的服务器上 (用户第一次点击页面需要联网,第二次点击时如果断网仍可访问该页面) import * as serviceWorker from './serviceWorker';
三、react中的组件
//App.js文件
//App组件即为react中一个简单的组件 render里面返回的内容即组件显示的内容
import React, { Component } from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {}
}
render() {
return (
<div>
hello world
</div>
);
}
}
// 导出组件
export default App;
//index.js组件文件
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App'; // 引入App组件
// 第三方模块 把App组件挂载到root节点中 root节点就会展示组件的内容
ReactDOM.render(<App />, document.getElementById('root'));
创建组件的两种方式
1)用class创建组件
import React from 'react';
import ReactDOM from 'react-dom';
if(module.hot){
module.hot.accept();
}
// 1) class创建组件
class App extends React.Component{
render(){
return (
<div>
<div>Hai Jess</div>
<div>Hai Alex</div>
</div>
)
}
}
ReactDOM.render(
<App/>,
document.getElementById('root')
)
2) 函数式创建组件
import React from 'react';
import ReactDOM from 'react-dom';
if(module.hot){
module.hot.accept();
}
// 2) 函数式创建组件
function App() {
return (
<div>
<div>Hai Jess</div>
<div>Hai Alex</div>
</div>
)
}
ReactDOM.render(
<App/>,
document.getElementById('root')
)
四、JSX语法
JSX语法:可扩展的js语法,不是字符串也不是html
-
- 结构顶层只能有一个元素
// 例如:
<div>hello world!</div><div>123</div>
//会报错
<div>
<div>hello world!</div>
<div>123</div>
</div>
//不会报错
-
- class必须写className
-
- 标签必须是闭合状态
<input type='text'/>
- 标签必须是闭合状态
-
- { }
- 可以执行JS表达式
- 花括号内有数组默认展开
- 如果元素属性是一个变量需要花括号 src={obj.xx}
- 花括号中不能写for循环
-
- 受控组件与非受控组件
- 如果在表单元素上设置一个默认值(value / checked),该元素为受控组件(即无法输入其他内容)
- 解决方案:加default
<input type='text' defaultValue="hahaha"/><input type='checkbox' defaultChecked/>
-
- dangerouslySetInnerHTML
在JSX中希望显示一些内容,不希望被自动转义(即在input输入框中输入
哈哈
,在页面中显示文字,不显示标签,即通过dangerouslySetInnerHTML属性来设置
dangerouslySetInnerHTML={{__html:item}}
<ul> { // 渲染页面 this.state.arr.map((item, i) => { return ( <li key={i} onClick={this.delItem.bind(this,i)} dangerouslySetInnerHTML={{__html:item}} ></li> ) }) } </ul> - dangerouslySetInnerHTML
在JSX中希望显示一些内容,不希望被自动转义(即在input输入框中输入
-
- label标签
// label内的for属性 要写成 htmlFor <label htmlFor="text">输入内容</label> <input id="text" className="input" type="text" onChange={this.inputChange.bind(this)} //为了让input框value值改变 必须要写onChange方法 value={this.state.val} />
五、实现一个todolist页面
//Todolist.js组件文件
import React, { Component,Fragment } from 'react';
// 引入Fragment占位符
class Todolist extends Component {
constructor(props) {
super(props);
this.state = {}
}
render() {
return (
// Fragment占位符会让最外层的结构不在页面中显示,即页面中会直接显示Fragment标签里面的结构内容
<Fragment>
<div>
<input />
<button>提交</button>
</div>
<ul>
<li>123</li>
<li>456</li>
<li>123</li>
<li>123</li>
</ul>
</Fragment>
);
}
}
export default Todolist;
六、react中的响应式设计和事件绑定
class Todolist extends Component {
constructor(props) { //接收props参数
super(props); //调用父类的构造函数
this.state = { //组件的状态
val: '',
arr: ['Tom','Jess']
}
}
//改变input框的value值
inputChange (ev){
console.log(ev.target.value);//input框中输入的value值
this.setState({ //setState方法会改变state状态中的数据,从而使页面发生改变
val: ev.target.value //让input中输入的值赋值给state状态的val
})
}
//点击提交按钮
btnClick(){
// copy数组,把val值添加到新数组中
let {arr,val} = this.state;
arr.push(val)
this.setState({arr,val:''})
}
//点击删除任务
delItem(id){
// immutable
// state 不允许做任何改变
console.log(id)
const arr = [...this.state.arr];
arr.splice(id,1);
this.setState({arr})
}
render() {
return (
// Fragment占位符会让最外层的结构不在页面中显示,即页面中会直接显示Fragment标签里面的结构内容
<Fragment>
<div>
<input
type="text"
onChange={this.inputChange.bind(this)} //为了让input框value值改变 必须要写onChange方法
value={this.state.val}
/>
<button
onClick={this.btnClick.bind(this)}
>提交</button>
</div>
<ul>
{
// 渲染页面
this.state.arr.map((item, i) => {
return (<li key={i} onClick={this.delItem.bind(this,i)}>{item}</li>)
})
}
</ul>
</Fragment>
);
}
}
七、组件之间的传值
在src文件夹下创建一个文件夹 components,即组件
父组件向子组件传递数据
- 父组件上给子组件中绑定自定义属性,把数据放到自定义属性上
- 子组件中使用
this.props.zdy去接收
//父组件
class App extends Component {
constructor() {
super();
this.state = {
arr: [11, 22, 33, 44, 55]
}
}
render() {
let {arr} = this.state;
let arr1 = arr.concat();
let list = arr1.map((item,i)=>{
return (
<List
{...{
text:item,
key:i,
a:123,
b:456,
c:789
}}
/>
)
})
return (
{/* <List data={this.state.arr}/> */}
<ul>{list}</ul>
)
}
}
//子组件
class List extends Component {
constructor(){
super();
this.state = {}
}
render(){
let {text,key,a,b,c} = this.props;
console.log(key);
return(
<li>{text}</li>
)
}
}
八、围绕react衍生的思考
react:
- 声明式开发 => 数据驱动 减少大量操作DOM的代码量
- 可以与其他框架共存
- 组件式开发 =>
- 普通标签与组件的区别:组件标签首字母大写
- 单向数据流
- 视图层框架:大型界面开发时,React.js 只负责视图层内容,我们还需要数据层框架(redux/Flux)等的支持。因为很明显,当复杂的组件关系之间,需要传递数据,React.js 会非常麻烦。
- 函数式编程 => 易维护 容易进行前端自动化测试
数据的单向流动
react数据单向流:子父组件之间的通讯(数据传递)规则,原则:数据只能从父组件传递到子组件,而不能由子组件直接修改父组件的数据,数据属于谁谁才有资格去修改;
-
情况一: 父级的数据传到子级,数据本身还是父级的,如果用户操作子级要改变传递的数据,那么不能子级改,要让父级修改 父级要定义一个修改数据的方法,在传递数据的时候也一起传给子级 当触发子级行为的时候,子级去调用修改父级数据的方法,然后父级收到后 子级的修改,父级修改数据,当父级的数据发生变化时,又把最新的数据传给子级
-
情况二: 父级把数据给了子级,子级只想在触发子级的时候,子级的数据改变,父级数据不改变 也就是,父级通过自定义的方式传数据给子级,子级可以在constructor中接收到父级传递的数据(就一次),把父级传递的这个数据,变为 this.state,自己就拥有了父级的数据,并且修改自己的数据不会影响到父级
九、PropTypes与DeaultProps
子组件接收父组件的参数,可能是函数,数字类型,字符串类型,这时需要基于PropTypes对属性接收做一个强校验
- 引入PropTypes:
import PropTypes from 'prop-types'; defaultProps: 在子组件给这个必传值设置一个默认值
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
class TodoItem extends Component {
constructor(props) {
super(props);
this.state = {}
}
//点击删除 子组件调用父组件
delItem = () => {
let { delItem, i } = this.props;
delItem(i);
}
render() {
let { content, test } = this.props;
return (
<Fragment>
<li
onClick={this.delItem}
>
{test}-{content}
</li>
</Fragment>
);
}
}
//属性强校验 在子组件限制父组件传值的类型,如果父组件没有按照子组件设置的类型传值会报错 不会影响代码执行,但是有利于开发
TodoItem.propTypes = {
test: PropTypes.string.isRequired, //表示test需要从父组件传递一个字符串类型的值,而且必须传值,如果父组件没有传值会报错
content: PropTypes.string,
delItem: PropTypes.func,
i: PropTypes.number
};
// 如果父组件确实无法传这个值(test),则需要在子组件给这个必传值设置一个默认值
TodoItem.defaultProps = {
test: 'hello eorld'
}
export default TodoItem;
关于propTypes在官网有更多的写法
import PropTypes from 'prop-types';
MyComponent.propTypes = {
// 你可以将属性声明为 JS 原生类型,默认情况下
// 这些属性都是可选的。
optionalArray: PropTypes.array,
optionalBool: PropTypes.bool,
optionalFunc: PropTypes.func,
optionalNumber: PropTypes.number,
optionalObject: PropTypes.object,
optionalString: PropTypes.string,
optionalSymbol: PropTypes.symbol,
// 任何可被渲染的元素(包括数字、字符串、元素或数组)
// (或 Fragment) 也包含这些类型。
optionalNode: PropTypes.node,
// 一个 React 元素。
optionalElement: PropTypes.element,
// 你也可以声明 prop 为类的实例,这里使用
// JS 的 instanceof 操作符。
optionalMessage: PropTypes.instanceOf(Message),
// 你可以让你的 prop 只能是特定的值,指定它为
// 枚举类型。
optionalEnum: PropTypes.oneOf(['News', 'Photos']),
// 一个对象可以是几种类型中的任意一个类型
optionalUnion: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.instanceOf(Message)
]),
// 可以指定一个数组由某一类型的元素组成
optionalArrayOf: PropTypes.arrayOf(PropTypes.number),
// 可以指定一个对象由某一类型的值组成
optionalObjectOf: PropTypes.objectOf(PropTypes.number),
// 可以指定一个对象由特定的类型值组成
optionalObjectWithShape: PropTypes.shape({
color: PropTypes.string,
fontSize: PropTypes.number
}),
// 你可以在任何 PropTypes 属性后面加上 `isRequired` ,确保
// 这个 prop 没有被提供时,会打印警告信息。
requiredFunc: PropTypes.func.isRequired,
// 任意类型的数据
requiredAny: PropTypes.any.isRequired,
// 你可以指定一个自定义验证器。它在验证失败时应返回一个 Error 对象。
// 请不要使用 `console.warn` 或抛出异常,因为这在 `onOfType` 中不会起作用。
customProp: function(props, propName, componentName) {
if (!/matchme/.test(props[propName])) {
return new Error(
'Invalid prop `' + propName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
},
// 你也可以提供一个自定义的 `arrayOf` 或 `objectOf` 验证器。
// 它应该在验证失败时返回一个 Error 对象。
// 验证器将验证数组或对象中的每个值。验证器的前两个参数
// 第一个是数组或对象本身
// 第二个是他们当前的键。
customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
if (!/matchme/.test(propValue[key])) {
return new Error(
'Invalid prop `' + propFullName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
})
};
十、props,state,render的关系
当组件的state或者props发生改变的时候,render函数就会重新执行 当父组件的render函数被运行时,它的子组件render都将被重新运行一次
十一、虚拟DOM
- 1)state 数据
- 2)JSX模板 => render函数下的结构
- 3)数据 + 模板 结合,生成真实的DOM,来显示
- 4)state发生改变
- 5)数据 + 模板 结合,生成真实的DOM,替换原始DOM
缺陷: 第一次生成了一个完整的DOM片段, 第二次生成了一个完整的DOM片段, 第二次的片段替换了第一次的片段, 这样十分消耗性能
改进方案1:
- 1)state 数据
- 2)JSX模板 => render函数下的结构
- 3)数据 + 模板 结合,生成真实的DOM,来显示
- 4)state发生改变
- 5)数据 + 模板 结合,生成真实的DOM,并不直接替换原始的DOM
- 6)新的DOM(Document Fragment文档碎片)和原始的DOM作比对,找差异
- 7)找出input框发生变化
- 8)只用新的DOM中的input,替换原始DOM中的input元素
缺陷: 新的DOM和原始的DOM作比对的过程也会消耗性能,性能提升并不明显
改进方案2:虚拟DOM
- 1)state 数据
- 2)JSX模板 => render函数下的结构
- 3)生成虚拟DOM(虚拟DOM就是一个真实的对象,用它来描述真实的DOM)
['div',{id:'abc'},['span,{},'哈哈']
- 4)用虚拟DOM的结构,生成真实的DOM,来显示
<div id="abc"><span>哈哈</span></div>
- 5)state发生变化
- 6)数据 + 模板 生成新的虚拟DOM (极大的提升性能)
['div',{id:'abc'},['span,{},'bye bye']
- 7)比较原始虚拟DOM和最新虚拟DOM的区别,此例即span中的内容(比对的是js对象,不是真实的DOM)
- 比较的过程用到
Diff算法(diffence)
- 比较的过程用到
- 8)直接操作DOM,改变span中的内容
虚拟DOM优点:
- 提升性能
- 使得跨端应用得以实现(React Native)
十二、虚拟DOM中的diff算法


十三、ref
可以快速的获取组件或者元素 在指定组件上写一个ref的属性,任意值
<App ref="app"/> // 定义组件
this.ref.app // 获得这个组件
十四、react中的生命周期函数
生命周期函数,是指在某一个时刻组件会自动调用执行的函数
14.1 mouting阶段 (只执行一次)
constructor初始化数据componentWillMount挂载之前render渲染 每次渲染时要处理的逻辑componentDidMount请求数据 获取到真实的DOM
constructor(){
// 初始化数据
super();
this.state = {}
console.log(1)
}
componentWillMount(){
console.log('挂载之前')
}
render(){
// 第一次渲染
console.log('渲染')
}
componentDidMount(){
// 请求数据
console.log('挂载之后')
}
14.2 updating阶段
shouldComponentUpdate性能优化 此函数必须有返回值,返回布尔值 默认为true 返回true 即更新 返回false 即不更新componentWillUpdate数据更新之前render数据渲染 (不要使用setState)componentDidUpdate数据更新之后
// updating阶段
shouldComponentUpdate(){
// 性能优化
// 此函数必须有返回值,返回布尔值 默认为true
console.log('should');
return true; // 返回true 即更新 返回false 即不更新
}
componentWillUpdate(){
console.log('更新之前')
}
componentDidUpdate(){
console.log('更新之后')
}
myClick=()=>{
let {num} = this.state;
num++;
this.setState({num})
}
render(){
console.log('渲染')
return(
<div id="box">
<button
onClick = {this.myClick}
>{this.state.num}</button>
</div>
)
}
- 注意:updating阶段不要使用setState,否则会死循环
componentWillReceiveProps父级数据发生变化- 一个组件要从父组件接收参数,如果这个组件第一次存在于父组件中,不会执行;如果这个组件之前已经存在于父组件中,才会执行
14.3 unmouting阶段
componentWillUnmout当组件死亡时触发(卸载、跳路由、关定时器、数据重置、变量置空、清除事件)
页面什么时候会被渲染
-
- props或者state发生改变时,页面会重新渲染
-
- 父组件发生改变,子组件会跟随父组件重新渲染(消耗性能)
- 性能优化方案:
- 在子组件写一个生命周期函数
shouldComponentUpdate(nextProps,nextState),返回flase,父组件执行,子组件不会再更新
十五、在react中发送ajax请求
- 安装模块
npm add axios
// todolist文件
//AJAX请求数据 只请求一次
componentDidMount(){
axios.get('/api/todolist')
.then(()=>{alert('success!')})
.catch(()=>{alert('errer')})
}
15.1 使用Charles 进行接口数据模拟
经实践,charles无法请求到数据,原因不明
解决方案:
可以把假数据放在项目中的public目录下,也可以请求到数据