简介
react 是一个用于构建用户界面的javascript库,他比较简单,模式就是组件化开发,方便管理,将1个大的页面划分成若干小组件,当然他也有路由模块,redux等,他可以将每个组件的逻辑和样式放到一起去。
组件化优点
- 可组合
- 可重用
- 可维护
搭建脚手架
我们知道 react使用的语法是jsx (javascript xml),在之前如果说php是JS IN HTML那么react可以说是HTML IN JS,对于jsx我们需要用babel解析,官方给推荐我们用脚手架,直接安装各种依赖,html属于xml.
npm install create-react-app -g
然后去当前项目文件夹创建项目
create-react-app myreact
安装之后会出现个myreact的文件夹,文件夹里有pakage.json文件
{
"name": "myreact",
"version": "0.1.0",
"private": true,
"dependencies": { //依赖
"react": "^16.4.1",
"react-dom": "^16.4.1",
"react-scripts": "1.1.4"
},
"scripts": {
"start": "react-scripts start", //启动命令
"build": "react-scripts build", // 构建
"test": "react-scripts test --env=jsdom", //测试
"eject": "react-scripts eject" //webpack配置
}
}
我们可以清晰的看到react可以自动帮助我们安装react 和react-dom的包,那么他们是干什么的呢?
与vue不同的是,vue有大量的api,,但是react的核心就几个,比如
- 虚拟DOM(对象)
- DOM_DIFF
(jsx像html,但是不是html) facebook 提供的jsx语法,可以将xml 'babel'成react对象,,比如ajax就是解析异步的xml,react渲染到页面的整个过程如下
babel转移JSX语法 => react语法 => 虚拟Dom => 插入到页面中
问们可以看到,在src的js里面,用的import,这个是webpack语法,import React from 'react';相当于node中的let {React} require('react');
react特点
- 在index.js引入 home.js
//index.js
import './home';
//编辑home.js
import React from 'react'; //创建元素
import {render} from 'react-dom'; //渲染页面dom
import './home.css';
//let element = <h1>hello baby <span>child</span></h1>
console.log(element)
//可以输出一个虚拟DOM,我们说虚拟dom其实是一个对象如下,babel编译出的react对象,大致特点
//我们可以模拟React, render
let React = {
createElement(type, props , ...children){ //类型,属性,儿子
return {type , props , children}
}
}
function render(vnode,container){
console.log(vnode)
console.log(typeof vnode === "string")
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);
}

jsx语法特点
- React.Fragment 无意义标签,相邻的两个jsx元素/react元素不能并列写,渲染数组,必须包起来
- 属性
- 不能用关键字
- class => className
- style={"color:red"} 将内容转换为对象
- 写注释{/注释/}
- for => htmlfor
- 将字符串变成标签 danerouslySetIneerHtml{{__html:str}} xxs攻击
- {}表达式的用法<%=%>
- 表达式取值得有返回值(函数可以返回jsx) 1.{放表达式/自执行函数/有返回值的函数执行}
- {abc + 'sdf' + saf}
- {不能放if表达式,,用三目运算符} null || void 0 都是空,合法的jsx语法
import React from 'react'; //创建元素
import {render} from 'react-dom'; //渲染页面dom
let arr = ['不吃饭','不睡觉'];
//使用map时需要增加key
function toLis(){
return arr.map((item,index)=>(<li key = {index}>{item + index}</li>))
}
let ele = <div>{toLis(1)}</div>;
console.log(ele)
render(ele, document.getElementById('root'))
组件化(复用,提高可维护性)
- 组件由react元素组成,组件就是一个函数
- 组件(函数组件,类组件) 属性 , 状态
- 假设我们要一个计时器,我们可以如下
function clock(time){
return <h4>时间是:{JSON.stringify(time)}</h4>
}
let ele = <div></div>
render(<div>{clock(new Date().toLocaleString())}</div> ,document.getElementById('root'))
使用函数组件

我们可以看到,将函数作为标签,bebel会将这个标签自动编译成element对象,方便render渲染,所以我们就可以直接渲染
function Clock(time){
return <h4>时间是:{JSON.stringify(time)}</h4>
}
// 组件传值,会自动将属性值传给函数组件
render(<Clock data={new Date().toLocaleString()} data_in="哈哈"></Clock> ,document.getElementById('root'))
结果=>
时间是:{"data":"2018/7/1 下午4:56:21","data_in":"哈哈"}
由此可见 我们想要获取 时间只需如下
function Clock(props){
return <h4>时间是:{props.data}</h4>
}
我们想每秒刷新时间,我们可以看见,这种方法只会渲染一次,所以我们可以是这每秒刷新
setInterval(() =>{
render(<Clock data={new Date().toLocaleString()}></Clock> ,window.root)
},1000)
这样我们成功实现了计时,看dom渲染中,我们可以清晰看出仅仅是time变化,而不是标签变化,这里涉及到我嗯dom-diff算法
同时,函数组件的缺点是:
- 没有生命周期
- 没有this
- 没有自己状态
如果需要以上之一,我们需要写类组件,那么类组件如何实现? 首先我们需要react 里的Component类
import React , {Component} from 'react'; //创建元素
import {render} from 'react-dom'; //渲染页面dom
class Clock extends Component{
constructor(props){
super();
this.state = {data:new Date().toLocaleString()}
}
componentDidMount(){// 组件挂在完成
console.log('didmoount')
setInterval(()=>{
//将这个对象和原有状态进行合并,合并后的结果重新渲染界面
this.setState({data:new Date().toLocaleString()})
},1000)
}
render(){ //每一个类组件都有一个render方法,将render的返回值作为结果进行渲染
console.log('render');
return (
//在render方法中可以通过this.props获取属性
<React.Fragment>
<p>{this.props.name}</p>
<div>{this.state.data}</div>
</React.Fragment>
)
}
}
render(<Clock name="当前时间是:"></Clock> ,window.root)
当然还有另一种我们经常用的方法
import React , {Component} from 'react'; //创建元素
import {render} from 'react-dom'; //渲染页面dom
class Clock extends Component{
constructor(props){
super();
this.state = {data:new Date().toLocaleString()}
}
getTime() {
setInterval(()=>{
//将这个对象和原有状态进行合并,合并后的结果重新渲染界面
this.setState({data:new Date().toLocaleString()})
},1000);
}
render(){ //每一个类组件都有一个render方法,将render的返回值作为结果进行渲染
const self = this;
self.getTime();
return (
//在render方法中可以通过this.props获取属性
<React.Fragment>
<p>{self.props.name}</p>
<div>{self.state.data}</div>
</React.Fragment>
)
}
}
render(<Clock name="当前时间是:"></Clock> ,window.root)
设置默认属性和属性校验
import React , {Component} from 'react'; //创建元素
import {render} from 'react-dom'; //渲染页面dom
//校验属性的正确性 npm install prop-types
import PropTypes from 'prop-types';
class Person extends Component{
//Es6不支持静态属性, 只有静态方法 这个是ES7的
static propTypes = {
name: PropTypes.string.isRequired ,
age : PropTypes.number,
gender: PropTypes.oneOf ([ '男' ,'女']),
hobby : PropTypes.arrayOf(PropTypes.string),
pos: PropTypes.shape({x: PropTypes.number,y: PropTypes.number}),
salary(props,propty){
if(props[propty] > 3000){throw new Error('salary too big')}
return props[propty] < 3000;
}
}
//设置默认属性
static defaultProps = {name:'zdl'}
constructor(props){super();}
render(){return (<h1>{this.props.age}</h1>)}
}
let person = {
name : 100 ,
age : '20' ,
gender: '男' ,
hobby : ['吃饭', '睡觉'],
pos: {x:100, y:200},
salary: 5000
}
render(<Person {...person}></Person>, window.root)
React生命周期
React最重要,最常用的就是生命周期,详情www.cnblogs.com/qiaojie/p/6…
这里我们只提供部分测试代码

//生命周期
import React , {Component} from 'react';
import ReactDom,{render} from 'react-dom';
class ChildCounter extends Component{
constructor(){
super();
this.state = {};
}
// 挂载之前,改组件会触发多次(不推荐使用),可以在constrctor初始化值
// componentWillMount(){
// console.log("child将要挂载");
// }
render(){
return <div>{this.props.num}</div>
}
// componentWillUpdate(){
// console.log("child更新完成");
// }
componentDidMount(){
console.log(this.state.num);
console.log("child挂载完成");
}
static getDerivedStateFromProps(newProps){
//ChildCounter使用getDerivedStateFromProps(),但也包含以下传统生命周期:
// componentWillMount
// componentWillReceiveProps
// componentWillUpdate
// 应删除上述生命周期
console.log("接受新属性新");
return {num : 1} //会将之前的状态覆盖掉
}
getSnapshotBeforeUpdate(prevProps , prevState){//更新前的属性和更新前的状态
console.log("更新前的属性和状态" + prevProps + prevState);
return '123'; //返回的值会传到componentDidUpdate里去
}
componentDidUpdate(a,b,c){
console.log("配合getSnapshotBeforeUpdate更新完毕" , a,b,c);
}
// //第一次不会执行,而且是唯一可以调用setState属性,已经被替换了
// componentWillReceiveProps(newProps){//接收到新的属性之后才会执行
// console.log("child接受到新属性");
// }
//只要调用setStat就会更新视图 返回false不更新 优化 immutablejs
shouldComponentUpdate(nextProps , nextState){
console.log("child将要更新视图");
return true;
}
}
class Person extends Component{
//内部可以声明状态,获取属性
constructor(props){
super();
this.state = {num: 0}
//this.fn = this.fn.bind(this)
}
// fn(){
fn = () => {
this.setState((prevState) => ({num: prevState.num + 1}));
// this.time = setInterval(()=>{
// this.setState((prevState) => ({num: prevState.num + 1}));
// },1000)
}
// 挂载之前,改组件会触发多次(不推荐使用),可以在constrctor初始化值
componentWillMount(){
console.log("将要挂载");
}
//卸载组件
remove = () =>{
console.log("销毁");
ReactDom.unmountComponentAtNode(window.root)
}
//组件销毁之前
componentWillUnmount(){
console.log("销毁之前");
clearInterval(this.time)
}
render(){
console.log("渲染");
return (<div>
{this.state.num}
<button onClick={this.fn}> + </button>
<button onClick={this.remove}> 删除组件</button>
<ChildCounter num={this.state.num}></ChildCounter>
</div>)
}
//组件挂载完成
componentDidMount(){
console.log("组件挂载完成");
}
//只要调用setStat就会更新视图 返回false不更新 优化 immutablejs
shouldComponentUpdate(nextProps , nextState){
console.log("更新视图");
return nextState.num%2;
}
}
let person = {
name : 'person'
}
render(<Person {...person}></Person>, window.root)