React
简介
用于构建用户界面的JavaScript库,声明式、组件化、一次学习,随处编写,高效,单向数据流,使用虚拟dom及Dom Diff算法最小化页面重绘。
依赖
react.js: React的核心库
react-dom.js: 提供操作DOM的react扩展库
babel.min.js: 解析JSX语法代码转为纯JS语法代码的库
hello
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello</title>
</head>
<body>
<div id ="main"></div>
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">//jsx ---> js
//1.创建虚拟dom
var vdom = <h1>Hello React!</h1>
//2.渲染虚拟dom到页面
ReactDOM.render(vdom,document.getElementById('main'))
</script>
</body>
</html>
JSX
javascript xml, react定义了一种类似于xml的js扩展语法,xml+js,用来创建react虚拟dom对象,语法规则,当遇见尖括号时,解析为标签,当遇见大括号时,解析为js,浏览器不能直接解析jsx代码,需要babel转换为js代码才可以运行,使用jsx需要给脚本标签加上type="text/babel",声明需要babel进行处理。
语法
ReactDOM.rander(virtualDOM,containerDOM)
将虚拟的DOM渲染成真实DOM
示例
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id = "dom1"></div>
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
const names = ['jQuery','Zepto','Angular','React','Vue']
const ul =(
<ul>
{names.map((name,index) => <li key={index}>{name}</li>)}
</ul>)
ReactDOM.render(ul,document.getElementById('dom1'))
</script>
</body>
</html>
模块化
向外提供特定功能的js程序,一般为一个js文件,用于复用js,简化js的编写,提高js运行的效率。
组件化
用来实现特定功能效果的代码集合,即一个页面的版块,包含组件的html、css、js、所有资源。
面向组件编程
组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello</title>
</head>
<body>
<div id ="main"></div>
<div id ="main2"></div>
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
//1.定义组件
var vdom = <h1>Hello React!</h1>
function Component() {
return <h2>这个标签来自一个组件</h2>
}
//无状态组件使用函数方式创建、效率更高
//类的方式具备状态
class Component2 extends React.Component{
render(){
return <h2>这个标签来自一个组件类</h2>
}
}
//2.渲染组件标签
ReactDOM.render(<Component />,document.getElementById('main'))
ReactDOM.render(<Component2 />,document.getElementById('main2'))
</script>
</body>
</html>
组件属性
state
State属性是组件的状态属性,页面中组件的重新渲染由此属性控制,其值是一个对象,可以包含多个数据。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello</title>
</head>
<body>
<div id ="main"></div>
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
//1.定义组件
class News extends React.Component{
constructor(props){
super(props)
this.state = {
isState:false
}//初始化状态
//绑定新方法this
this.handleClick = this.handleClick.bind(this)
//必须赋值 否则 函数中的state是空值
}//调用父类构造
//新增方法 this 为空
handleClick(){
console.log(this)
const isState = !this.state.isState
this.setState({isState})
}
//重写方法
render(){
const {isState} = this.state
return <h2 onClick={this.handleClick}>{isState?'发布新闻':'没有新闻'}</h2>
}
}
//2.渲染组件标签
ReactDOM.render(<News />,document.getElementById('main'))
</script>
</body>
</html>
- 构造器初始化状态、绑定状态
- render方法读取状态
- 事件函数中修改状态
props
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello</title>
</head>
<body>
<div id ="man"></div>
<div id ="woman"></div>
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/javascript" src="../js/prop-types.js"></script>
<script type="text/babel">
//1.创建虚拟dom
function Persion(props) {
return(
<ul>
<li>姓名:{props.name}</li>
<li>年龄:{props.age}</li>
<li>性别:{props.sex}</li>
</ul>
)
}
Persion.defaultProps = {
sex: 'man',
age: 18
}//默认值
Persion.propTypes={
name: PropTypes.string.isRequired,
age: PropTypes.number
}//数据校验及数据是否为空
const p1 = {
name: 'Wang',
age: 18,
sex: 'girl'
}
const p2 = {
name: 'Lee',
age: 'cdef',
sex: 'girl'
}
class Persion2 extends React.Component{
render(){
return(<ul>
<li>姓名:{this.props.name}</li>
<li>年龄:{this.props.age}</li>
<li>性别:{this.props.sex}</li>
</ul>)
}
}
//2.渲染虚拟dom到页面
//ReactDOM.render(<Persion name={p1.name} age={p1.age} sex={p1.sex} />,document.getElementById('man'))
ReactDOM.render(<Persion2 {...p1} />,document.getElementById('man')) //三点运算符
ReactDOM.render(<Persion name={p2.name} age={p2.age}/>,document.getElementById('woman'))
</script>
</body>
</html>
打包运算 ...p1即可将p1中的值全部对应赋值给Persion组件/function fn(...list)调用fn(1,2,3) 此时为打包
refs和事件处理
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello</title>
</head>
<body>
<div id ="main"></div>
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
//1.创建虚拟dom
class Com extends React.Component{
constructor(props){
super(props)
this.showInput = this.showInput.bind(this)
this.handleBlur = this.handleBlur.bind(this)
//将组件绑定到函数
}
handleBlur(event){
alert(event.target.value)
}
showInput(){
const input = this.input
alert(input.value)
}
render(){
return(
<div>
<input type="text" ref="content"/>
<input type="text" ref={(input) => this.input = input}/>
//dom绑定到组件
<button onClick={this.showInput}>提示输入</button>
<input placeholder="失去焦点提示内容" onBlur={this.handleBlur}/>
//事件绑定
</div>
)
}
}
//2.渲染虚拟dom到页面
ReactDOM.render(<Com/>,document.getElementById('main'))
</script>
</body>
</html>
组件界面开发流程
- 拆分组件:拆分界面,抽取组件,
- 实现静态组件:使用组件实现静态页面效果
- 实现动态组件
- 动态显示初始化数据
- 实现交互功能
//第一阶段
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello</title>
</head>
<body>
<div id ="main"></div>
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
class App extends React.Component{
render(){
return(
<div>
<h1>This is To Do List</h1>
<Add />
<List />
</div>
)
}
}
class Add extends React.Component{
render(){
return(
<div>
<input type="text"/>
< button> Add #4</button>
</div>
)
}
}
class List extends React.Component{
render(){
return(
<ul>
<li>aaa</li>
<li>aaa</li>
<li>aaa</li>
<li>aaa</li>
<li>aaa</li>
</ul>
)
}
}
ReactDOM.render(<App/>,document.getElementById('main'))
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello</title>
</head>
<body>
<div id="main"></div>
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/javascript" src="../js/prop-types.js"></script>
<script type="text/babel">
class App extends React.Component {
/**
* 数据需要放在哪个组件,独占数据放到组件,共享数据放到父组件
* @returns {*}
*/
constructor(props) {
super(props)
this.state = {
todos: ['eat', 'sleep', 'coding']
}
this.addTodo = this.addTodo.bind(this)
}
addTodo(todo) {
const {todos} = this.state
todos.unshift(todo)
this.setState({todos})
}
render() {
const {todos} = this.state
return (
<div>
<h1>This is To Do List</h1>
<Add count={todos.length} addTodo={this.addTodo}/>
<List todos={todos}/>
</div>
)
}
}
class Add extends React.Component {
constructor(props) {
super(props)
this.add = this.add.bind(this)
}
add() {
const todo = this.todoInput.value.trim()
if (!todo) {
alert("不能为空!")
return
}
this.props.addTodo(todo)
this.todoInput.value
//读取数据
//校验数据
//添加
}
render() {
return (
<div>
<input type="text" ref={input => this.todoInput = input}/>
< button onClick={this.add}> Add #{this.props.count + 1}</button>
</div>
)
}
}
Add.propTypes = {
count: PropTypes.number.isRequired,
addTodo: PropTypes.func.isRequired
}
class List extends React.Component {
render() {
return (
<ul>
{this.props.todos.map((todo, index) => <li key={index}>{todo}</li>)}
</ul>
)
}
}
List.propTypes = {
todos: PropTypes.array.isRequired,
}
ReactDOM.render(<App/>, document.getElementById('main'))
</script>
</body>
</html>
//示例 阻止默认行为 组件取值
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello</title>
</head>
<body>
<div id="main"></div>
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
//1.创建虚拟dom
class LoginForm extends React.Component {
constructor(props) {
super(props)
this.handleSubmint = this.handleSubmit.bind(this)
this.handleChange = this.handleChange.bind(this)
}
handleChange(event) {
const pwd = event.target.value
this.setState({pwd})
}
handleSubmit(event) {
const name = this.nameInput.value
const {pwd} = this.state
event.preventDefault()
alert(`用户名:${name} 密码:${pwd}`)
}
render() {
return (
<form action="/test" onSubmit={this.handleSubmint}>
用户名:<input type="text" ref={input => this.nameInput = input}/>
密码:<input type="password" onChange={this.handleChange}/>
<input type="submit" value="登录"/>
</form>
)
}
}
ReactDOM.render(<LoginForm/>, document.getElementById('main'))
</script>
</body>
</html>
组件生命周期
- 组件的三个生命周期状态:
- Mount:插入真实 DOM
- Update:被重新渲染
- Unmount:被移出真实 DOM
- 生命周期流程:
- 第一次初始化显示: ReactDOM.render(, containDom)
- constructor()
- componentWillMount() : 将要插入回调
- render() : 用于插入虚拟DOM回调
- componentDidMount() : 已经插入回调
- 每次更新state: this.setState({})
- componentWillReceiveProps(): 接收父组件新的属性
- componentWillUpdate() : 将要更新回调
- render() : 更新(重新渲染)
- componentDidUpdate() : 已经更新回调
- 删除组件: ReactDOM.unmountComponentAtNode(div): 移除组件
- componentWillUnmount() : 组件将要被移除回调
- 常用的方法
- render(): 必须重写, 返回一个自定义的虚拟DOM
- constructor(): 初始化状态, 绑定this(可以箭头函数代替)
- componentDidMount() : 只执行一次, 已经在dom树中, 适合启动/设置一些监听
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello</title>
</head>
<body>
<div id="main"></div>
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
class Life extends React.Component {
constructor(props) {
super(props)
this.state = {
opacity: 1
}
this.distroyComponent = this.distroyComponent.bind(this)
}
distroyComponent() {
ReactDOM.unmountComponentAtNode(document.getElementById('main'))
}
componentWillUnmount() {
clearInterval(this.intervalId)
}
componentDidMount() {
this.intervalId = setInterval(function () {
let {opacity} = this.state
console.log("running")
opacity -= 0.1
if (opacity <= 0) {
opacity = 1
}
this.setState({opacity})
}.bind(this), 200)
}
//render为渲染
render() {
const {opacity} = this.state
return (
<div>
<h2 style={{opacity}}>{this.props.msg}</h2>
<button onClick={this.distroyComponent}>结束</button>
</div>
)
}
}
ReactDOM.render(<Life msg="This is Msg"/>, document.getElementById('main'))
</script>
</body>
</html>
虚拟DOM与DOM Diff算法
虚拟DOM
一个虚拟DOM(元素)是一个一般的js对象, 准确的说是一个对象树(倒立的)
虚拟DOM保存了真实DOM的层次关系和一些基本属性,与真实DOM一一对应
如果只是更新虚拟DOM, 页面是不会重绘的
Virtual DOM 算法的基本步骤
用JS对象树表示DOM树的结构;然后用这个树构建一个真正的DOM树插到文档当中
当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异
把差异应用到真实DOM树上,视图就更新了
进一步理解
Virtual DOM 本质上就是在 JS 和 DOM 之间做了一个缓存。
可以类比 CPU 和硬盘,既然硬盘这么慢,我们就在它们之间加个缓存:既然 DOM 这么慢,我们就在它们 JS 和 DOM 之间加个缓存。CPU(JS)只操作内存(Virtual DOM),最后的时候再把变更写入硬盘(DOM)。