react介绍
react是一个轻量级的视图层框架,用于构建用户界面的js库,核心是专注于视图层,目的是实现组件化开发
react生态:React + React-Router + Redux + Axios + Babel + Webpack
初始化react项目
- 全局安装create-react-app脚手架
// 如果已经安装了,直接进行第2步操作
npm install create-react-app -g
- 安装完成后就可以创建react项目
cd desktop // cd到桌面上,执行下面操作
create-react-app 项目名 // 创建项目
- 项目创建完成后,cd到项目中,启动服务
cd 项目名 // cd到项目中,执行下面操作
yarn start // 启动服务
服务启动完成后就会跳出来一个react页面
用编辑器打开项目文件,会发现文件夹里已经出现许多文件
文件简要介绍:
- public文件:
1、favicon.ico:react图标,可以替换成自己的图标,也可以直接删除
2、index.html:html文件,不能删
3、manifest.json:如果使用了PWA就可以对manifest.json进行配置,创建应用快捷键。 - src文件:
这里有三个文件比较特殊
1、index.js:入口文件,除了此文件,src文件中其他的都可以删除
2、App.test.js: 自动化测试文件。
3、serviceWorker.js: PWA (progressive web application) 帮助我们借助网页写一些手机APP应用 https协议的服务器上,第一次打开需要联网,后面再次访问时就可以访问缓存的页面 - .gitignore:不想在git上显示的文件可以在这里配置
- package.json:项目依赖文件
- README.md:备注,里面内容可删除,可以在这里编辑一些自己的备注
- yarn.lock:依赖文件
如果想要显示更多的文件,可以执行yarn eject,将隐藏的webpack配置文件config暴露出来,以便我们修改配置文件,如配置less文件
jsx语法
jsx是一种js和html混用的代码,由 Facebook 开发,代码示例:
let str = '<img src="https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/logo_top_86d58ae1.png" alt=""/>';
let arr = [1,3,4];
// jsx语法
let ele = (
<>
{/*当有2个或多个相邻的元素时,外面需要用一层标签包裹,如果不想让外面的标签占位,可以用<></>或者<Fragment></Fragment>表示*/}
<h1 style={{backgeound:'red'}}>标题</h1>
{/* 行内样式写法,style={{background:'red'}}一般是双大括号,外面表示里面是js语法,里面表示包裹的是对象,即一个js对象*/}
<div>{1==1 ? <span>对的</span> : <span>错的</span>} 三元表达式</div>
{/* jsx为了识别是html还是js分别用 < 和 { 区分,所以js外面要包层 {} */}
{/* {} 表示里面包裹的可能是 js、三元表达式、取值,只要有内容返回页面就能显示 */}
<div className="box">定义类名用 calssName</div>
{/* 属性的名字会有变化,例如 class是jsx里面的关键字,所以定义类名的时候 class转化为=>className,还有 for转化为=> htmlFor */}
<label htmlFor="text">此时要用htmlFor代替for</label>
{/* 将img插入到页面中 当前是一个危险的操作,不建议使用 */}
<div dangerouslySetInnerHTML={{__html:str}}></div>
{/* v-html把内容当成html插入到页面中 dangerouslySetlnnerHTML={{__html:xxx}} */}
{/* 列表渲染用map 因为map默认有返回值,数组可以直接渲染到页面上 */}
{arr.map((list,key) => (
// key一定要有,是为了后续的diff算法比对数据用,循环谁就在谁上面加
// key属性被内置了,不会作为属性传递
<li key={key}></li>
))}
</>
);
render函数
// 引入react核心包
import React from 'react'
// render,主要是用来渲染页面
import { render } from 'react-dom'
这里要注意的是,导入的React必须大写?
原因:
jsx语法是javascript + html混写,需要用babel转译。 babel转移时,会转换成 React.createElement 写法,要是react小写就识别不了。
代码如下:
// jsx 语法
let h1 = <h1 id="hello">hello world</h1>;
// 转移后的代码
React.createElement('h1',{id:'hello'},['hello world']);
通过 React.createElement 来创建一个 虚拟DOM,其中三个参数分别是type、props和children
- type:对应的标签是h1
- props:对应的是{id:'hello'},没有可直接用null代替
- children:对应的子节点,可以是多个
多个子节点的代码结构如下:
// 多个子元素(React.createElement层层嵌套)
let h1 = <h1 id="hello">hello world <span>你好</span></h1>;
// babel编译代码:
React.createElement('h1',{id:'hello'},['hello world',React.createElement('span',null,['你好'])]);
由此我们简单的梳理一下整个渲染的执行顺序
jsx语法 -> createElement格式 -> 转化成虚拟DOM(对象,可以描述当前元素) -> render函数渲染到页面上
基于此,我们可以实现一个简易版本的render函数
// 自定义模块
let React = {
createElement(type,props,...children){
console.log(...children);
return {
type,
props,
children
}
}
}
// 渲染函数
let render = (vnode,container) => {
if(typeof vnode == 'string') return container.appendChild(document.createTextNode(vnode ));
let {type,props,children} = vnode;
let ele = document.createElement(type);
for(let key in props){
ele.setAttribute(key,props[key]);
}
children.forEach(child => {
render(child,ele);
});
container.appendChild(ele);
};
let h1 = <h1 id="hello">hello world <span>你好</span></h1>;
// console.log(h1);
render(h1,window.root);
组件
react中,函数就是一个组件,并且组件名必须大写,是为了和jsx元素区分
组件分为:函数组件和类组件
函数组件:
- 没有this指向
- 没有生命周期
- 没有状态
import React from 'react'
// import ReactDOM from 'react-dom'
// 如果用的是上面的方法,没有解构render函数,下面的render()函数就变成ReactDOM.render()
import { render } from 'react-dom'
// 所有的组件都有属性(使用组件的地方,可以提供这些属性)
function Clock(props){
// toLocaleString() 根据本地时间把Date对象转换为字符串,并返回结果
return <h1>当前时间 {props.data.toLocaleString()}</h1>
// 渲染
render(<div>
// date就是提供的属性
<Clock date={new Date()}/>
</div>,window.root)
}
类组件:
- this指向的实例
- 有生命周期
- 有状态 this.state,它是个对象
// 实现一个类
// React.Component 是父类,它提供了一个更改自己的方法setState
class Clock extends Component{
constructor(props){
console.log(props);
super(props);
this.state = { // this.state这个名字不能修改,固定写法
date:new Date().toLocaleString()
};
}
// 跟上面的this.state是同一个,不同的写法,取一即可
// state = {};
// 生命周期函数 组件已经挂载完成
componentDidMount(){
this.timer = setInterval(()=>{
this.setState({ // 调用setState会更新视图
date:new Date().toLocaleString()
});
},1000)
}
// 组件将要更新
componentWillUpdate(){
// 在调用handleDelete前把定时器清除,不然会报错
// 组件已经卸载,但是定时器还在执行,会出错
clearInterval(this.timer);
}
handleClick(){
console.log(this); // undefined 外界调用类里面的函数是不合法的,所以要绑定this
}
// 这两种改变this的优缺点:
// 上面绑定this,每次调用函数都会产生一条函数
// 下面的箭头函数不好传参数,要是传参数可以在调用的地方在包一层箭头函数 (params)=>this.handleDelete
handleDelete = ()=>{
console.log(this); // this指向的实例
ReactDOM.unmountComponentAtNode(window.root); // 从这个节点上卸载组件
};
render(){
// console.log(this);
return (<div>
<h1>当前时间 {this.state.date} </h1>
<button onClick={this.handleClick.bind(this)}>点击</button>
<button onClick={this.handleDelete}>点击删除</button>
</div>)
}
}
render(<Clock />, window.root);
setState
import React,{Component} from 'react';
import {render} from "react-dom"
// 属性传到组件中不能更改
// 如果需要更改 把属性变为组件的状态
// 组件的数据来源 状态是自己的(可以更改),属性是外面的(不能更改)
class Counter extends Component{
state = {
count: this.props.count
};
handleClick = () => {
// setState 批量更新的操作 但不是一直更新,只有第一次默认是批量更新
// 多次执行setState,会批量执行,会进行合并,相同的key,后面的会覆盖前面的
// this.setState({
// count: this.state.count +1
// });
// this.setState({
// count: this.state.count +2
// });
// this.setState({
// count: this.state.count +3
// });
// 为什么setState放到 setTimeout 中会渲染多次?
// 会先执行transcation函数,此时isBatchingUpdate=false就不是批量操作,函数依次执行
// setTimeout(()=>{
// this.setState({
// count: this.state.count +1
// });
// this.setState({
// count: this.state.count +2
// });
// this.setState({
// count: this.state.count +3
// });
// },0);
// setTimeout 这种操作是不合法的 react提供了下面这种方式
// 每次都是基于上一次结果 在进行操作
this.setState((prevState)=>({ count: prevState.count + 1 }));
this.setState((prevState)=>({ count: prevState.count + 2 }));
this.setState((prevState)=>({ count: prevState.count + 3 }));
// => 等同于 可以在回调函数里面进行dom操作
this.setState({count:this.state.count+1},()=>{
this.setState({count:this.state.count+2},()=>{
this.setState({count:this.state.count+3})
})
})
};
render(){
return (<div>
{this.state.count}
<button onClick={this.handleClick}>添加</button>
</div>)
}
}
render(<Counter count={10}/>,window.root);
衍生的问题:为什么 setState 放到 setTimeout 中会渲染多次?
// 默认批量更新
let isBatchingUpdate = true;
let transcation = (component) => {
component.state = component.pendingState;
component.render();
// 第二次就不默认批量更新了
isBatchingUpdate = false;
};
class My {
constructor(){
this.state = {number:0}; // 自己的状态
this.pendingState = { ...this.state }; // 队列 存放状态
}
setState(obj){
if(isBatchingUpdate){
this.pendingState = {...this.pendingState,...obj};
}else {
this.pendingState = {...this.pendingState,...obj};
transcation(this)
}
}
update(){ // setState批量更新函数
/*
在React的生命周期钩子和合成事件中,多次执行setState,会批量执行
具体表现为,多次同步执行的setState,会进行合并,相同的key,后面的会覆盖前面的
当遇到多个setState调用时候,会提取单次传递setState的对象,把他们合并在一起形成一个新的
单一对象,并用这个单一的对象去做setState的事情,相同的key,后一个key值会覆盖前面的key值
*/
this.setState({number: this.state.number + 1});
this.setState({number: this.state.number + 2});
this.setState({number: this.state.number + 3});
// 为什么放到 setTimeout 中会渲染多次?
// 因为会先执行transcation函数,此时isBatchingUpdate=false就不是批量操作,函数依次执行
setTimeout(()=>{
this.setState({number: this.state.number + 1});
this.setState({number: this.state.number + 2});
this.setState({number: this.state.number + 3});
},0);
transcation(this);
}
render(){
console.log(this.state.number);
}
}
let my = new My();
// 内部会先调用此函数
my.update();
导出的几种方式
// 如果是默认导出的
import xxx from './xxx'
// 如果是一个个导出的
import { xxx } from './xxx'
import * as xxx from './xxx'
受控组件
// 受控组件
// 和状态相关的,状态一变视图跟着变化 类似 双向数据绑定
import React,{Component} from 'react'
import ReactDOM from 'react-dom'
class Control extends Component{
state = {
username: 'zx',
password: '123455'
};
handleChange = (e) => {
// console.log(e.target.value);
console.log(e.target);
this.setState({
// username: e.target.value,
// 上面的写法只适合页面只有一个input时,如果有多个,需要给每个不同input绑定不同的函数
// 下面的可以控制多个input,但需要给input设置name,并且需要和后面的状态名保持一致
[e.target.name]: e.target.value
})
};
handleClick = (e) => {
e.preventDefault();
alert(JSON.stringify(this.state))
};
render(){
return (<div>
{/*form 自带表单验证*/}
<form onSubmit={this.handleClick}>
{/*required 表单提交是自动校验输入框是否为空*/}
<input required type="text" name="username" value={this.state.username} onChange={this.handleChange}/>
<input type="text" name="password" value={this.state.password} onChange={this.handleChange}/>
<button type="submit">提交</button>
</form>
</div>)
}
}
ReactDOM.render(<Control/>,window.root);
非受控组件
// 如果只是点击获取输入框的值,推荐使用非受控组件
import React,{Component} from 'react'
import ReactDOM from 'react-dom'
class Control extends Component{
// 与第二种配合使用 把当前的password绑定到current属性上
password = React.createRef();
handleClick = (e) => {
e.preventDefault();
console.log(this.username.value);
// 第二种方式比第一种应用要多一层
console.log(this.password.current.value);
};
render(){
return (<div>
{/*form 自带表单验证*/}
<form onSubmit={this.handleClick}>
{/*required 表单提交是自动校验输入框是否为空*/}
{/*第一种方式*/}
{/*dom 指的就是当前的输入框 把输入框挂在username的实例上 */}
<input required type="text" name="username" ref={(dom)=>this.username=dom} />
{/*第二种方式*/}
<input type="text" name="password" ref={this.password}/>
<button type="submit">提交</button>
</form>
</div>)
}
}
ReactDOM.render(<Control/>,window.root);
属性传递 组件传值
组件传值是单向数据流
- 1、父子组件可以通过属性传递
- 2、平级组件之间传递是通过共同的父亲
- 3、多级组件,跨组件传递通过 contentApi
prop-types 用来校验属性类型 (第三方插件)
import React,{Component} from 'react'
import ReactDOM from 'react-dom'
// 用来校验属性类型
import PropTypes from 'prop-types'
class Person extends Component {
// 默认属性必须名字叫 defaultProps 属于类上的属性 es7
static defaultProps = {
name: 'zx'
};
// 专门用来校验类型的
static propTypes = {
age: PropTypes.number.isRequired,
gender: PropTypes.oneOf(['男','女']),
position: PropTypes.shape({
x: PropTypes.number,
y: PropTypes.number
}),
hobby: PropTypes.arrayOf(PropTypes.string),
salary(props,propName,componentName){
if(props[propName] <= 10000){
throw new Error('收益太低')
}
}
};
render(){
return <div>
hello welcome {this.props.name}
</div>
}
}
let obj = {
name:'zx',
age: 27,
gender: '男',
position: {
x: 100,
y: 100
},
hobby:['睡觉'],
salary: 1000
};
export {
Person,
obj
}
ReactDOM.render(<Person {...obj}/>,window.root);