1. React的诞生
1.1 JS加减法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<div>
<span id="result">0</span>
<button id="add">+</button>
<button id="minus">-</button>
</div>
</body>
<script>
let result = document.querySelector('#result');
let add = document.querySelector("#add");
let minus = document.querySelector("#minus");
add.addEventListener('click', function(){
let number = parseInt(result.innerText, 10);
numer += 1;
result.innerText = number;
})
minus.addEventListener('click', function(){
let number = parseInt(result.innerText, 10);
numer -= 1;
result.innerText = number;
})
</script>
</html>
1.2 简化思维
- 操作完填到页面,不从页面取,不去访问页面
1.3 代码版本进化
- 初步版本
<body>
<div id="root"></div>
<button id="add"></button>
</body>
let number = 0;
render();
let add = document.querySelector("#add")
add.onclick = () => {
number += 1;
render();
}
// js中创建,操作,最后渲染页面
function render(){
let span = React.createElement('span', {className: 'red'}, number);
ReactDOM.render(span, document.querySelector('#root'));
}
1.3.1 改进版本1
<body>
<div id="root"></div>
</body>
<script>
let number = 0;
let onClickButton = () =>{
number += 1;
render();
}
let onClickButton2 = () =>{
number -= 1;
render();
}
render();
function render(){
let span = React.createElement('span', {className: 'red'}, number);
let button = React.createElement('button', {onClick: onClickButton}, '+');
let button2 = React.createElement('button2', {onClick: onClickButton2}, '-');
let div = React.createElement('div', {className: 'parent'}, span, button, button2);
ReactDOM.render(div, document.querySelector('#root'));
}
</script>
1.3.2 改进版本2
<body>
<div id="root"></div>
</body>
<script>
let h = React.createElement
let number = 0;
let onClickButton = () =>{
number += 1;
render();
}
let onClickButton2 = () =>{
number -= 1;
render();
}
render();
function render(){
let div = h('div', {className: 'parent'},
h('span', {className: 'red'}, number),
h('button', {onClick: onClickButton}, '+'),
h('button2', {onClick: onClickButton2}, '-')
);
ReactDOM.render(div, document.querySelector('#root'));
}
</script>
- babel翻译
onClick={add()} 加括号写法,函数返回值赋值给onClick,错误
1.4 JSX版本
let number = 0
let add = ()=>{
number += 1
render()
}
let minus = ()=>{
number -= 1
render()
}
render()
function render(){
ReactDOM.render(
<div>
<span>{number}</span>
<button onClick={add}>+</button>
<button onClick={minus}>-</button>
</div>,
document.querySelector('#root')
)
}
- 虚拟DOM:表示DOM节点的对象,非真实的DOM,如上代码
2. 函数组件和Class组件
2.1 参数传递
function render(){
ReactDOM.render(
<App/>, // 即 React.createElement(App) 语法糖
document.querySelector('#root')
)
}
2.1.1 函数组件参数传递
标签的属性就是函数的参数
function App(){
return (
<div>
<Box1 name="booooooox1"/> // 标签的属性就是函数的参数
<Box2 name="booooooox2"/>
</div>
)
}
function Box1(obj){
return(
<div>{obj.name}</div> // 不要修改传来的属性,有bug,只能读取
)
}
函数组件没有自己的状态, Class组件有
2.2.2 Class组件参数传递
class App2 extends React.Component{
render() { //局部render
<div>
{this.props.name}
{this.props.age}
</div>
}
}
function render() {
ReactDOM.render(
<App2/ name="paul" age="10">,
document.querySelector('#root')
)
}
2.2.3 进化过程
// function组件传递参数过程
function App() {
return(
<div> app
<Foo message = "你好"/>
</div>
)
}
function Foo(props) {
return(
<div> 得到的message{props.message}</div>
)
}
ReactDOM.render(<App/>, document.querySelector("#app"))
/**
此时Foo组件想回传一个click事件给App组件,App组件用function就不适用了
子组件Foo想通知App组件改变Message
**/
Class App extends React.Component {
constructor() {
super()
this.state = {
message: '你好'
}
}
changeMessage() {
this.setState({ message: 'hello'})
}
render() {
return(
<div> app
<Foo message = {this.state.message} fn = {this.changeMessage.bind(this)} />
</div>
)
}
}
function Foo(props) {
return(
<div> 得到的message{props.message}
<button onClick = {props.fn} >Click</button>
</div>
)
}
ReactDOM.render(<App/>, document.querySelector("#app"))
2.2 代码示例
2.2.1 this为undefined的处理
<button onClick={this.add.bind(this)}>+</button>
or
<button onClick={() => this.add}>+</button>
2.2.2 组件代码
Class出现的原因是需要局部变量,不要全局变量
setState会减少更新损耗
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
<script src="https://cdn.bootcss.com/react/16.4.0/umd/react.production.min.js"></script>
<script src="https://cdn.bootcss.com/react-dom/16.4.0/umd/react-dom.production.min.js"></script>
</head>
<body>
<div id="root">
</div>
</body>
</html>
function App(props){
return (
<div>
<Box1 name="frank"/>
<Box2 name="jack"/>
</div>
)
}
class Box1 extends React.Component{
constructor(props){
super(props)
this.state = {
number: 0
}
}
add(){
this.setState({
number: this.state.number + 1
})
}
minus(){
this.setState({
number: this.state.number - 1
})
this.state.number -= 1
render()
}
render(){
return (
<div className="red">
<span>{this.state.number}</span>
<button onClick={this.add.bind(this)}>+</button>
<button onClick={this.minus.bind(this)}>-</button>
{this.props.name}
</div>
)
}
}
class Box2 extends React.Component{
constructor(props){
super(props)
this.state = {
number: 0
}
}
add(){
this.setState({
number: this.state.number + 2
})
}
minus(){
this.setState({
number: this.state.number - 2
})
}
render(){
return (
<div className="red">
<span>{this.state.number}</span>
<button onClick={this.add.bind(this)}>+2</button>
<button onClick={this.minus.bind(this)}>-2</button>
{this.props.name}
</div>
)
}
}
render()
function render(){
ReactDOM.render(
<App/>, // React.createElement(App)
document.querySelector('#root')
)
}
this.setState((state)=> {
return ( number: state.number + 1 )
})
3. 组件通信
3.1 父子组件通信
3.1.1 参数逐层传递下去 props, 祖传
3.1.2 子往上传,需要祖先传函数下来,子孙调用函数传回祖先
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.4.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.4.0/umd/react-dom.production.min.js"></script>
<title>JS Bin</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
.header{
display: flex;
justify-content: center;
}
.track{
border-bottom: 1px solid black;
}
*{margin:0;padding: 0; box-sizing: border-box;}
.playground{
border: 1px solid black;
background: green;
}
body{
padding: 40px;
}
class App extends React.Component{
constructor(){
super()
this.state = {
result1: 0,
result2: 0
}
this.t0 = new Date()
}
success1(x){
console.log(x)
console.log('兔子跑完了,用时')
let r1 = new Date() - this.t0
this.setState({
result1: r1
})
}
success2(x){
console.log(x)
console.log('乌龟跑完了,用时')
let r2 = new Date() - this.t0
this.setState({
result2: r2
})
}
render(){
return (
<div>
<div class="header">
<Time1 result={this.state.result1}/>
<Time2 result={this.state.result2}/>
</div>
<Playground success1={this.success1.bind(this)}
success2={this.success2.bind(this)}/>
</div>
)
}
}
// 顶部计时器
function Time1(props){
return (
<div>
<h2>🐇用时</h2>
<div>{props.result}</div>
</div>
)
}
function Time2(props){
return (
<div>
<h2> 🐢用时</h2>
<div>{props.result}</div>
</div>
)
}
// 操场 包含了兔子和乌龟跑道以及计时器功能
function Playground(props){
let success1 = props.success1
let success2 = props.success2
return (
<div class="playground">
<Track1 success={success1} />
<Track2 success={success2}/>
</div>
)
}
// 兔子跑道
class Track1 extends React.Component{
constructor(){
super()
let n = 0
this.state = {
style: {
transform: `translateX(${n}%)`
}
}
let timerId = setInterval(()=>{
n+=25
this.setState({
style: {
transform: `translateX(${n}%)`
}
})
if(n>=100){
window.clearInterval(timerId)
this.props.success('我是兔')
}
},1000)
}
render(){
return (
<div>
<div class="player" style={this.state.style}>🐇</div>
<div class="track"></div>
</div>
)
}
}
// 乌龟跑道
class Track2 extends React.Component{
constructor(){
super()
let n = 0
this.state = {
style: {
transform: `translateX(${n}%)`
}
}
let timerId = setInterval(()=>{
n+=20
this.setState({
style: {
transform: `translateX(${n}%)`
}
})
if(n>=100){
window.clearInterval(timerId)
this.props.success('我是龟')
}
},1000)
}
render(){
return (
<div>
<div class="player" style={this.state.style}>🐢</div>
<div class="track"></div>
</div>
)
}
}
ReactDOM.render(<App></App>, document.querySelector('#root'))
- 运行结果
3.2 任意组件通信
3.2.1 非单向数据流
- Son2花钱之后发布,Son3订阅,得到数据,但是每个组件都需要发布和订阅事件,太过繁琐
- 思路:每个组件花钱后发布,剩余所有组件订阅,才能统一数据
// 家族资产总量
var money = {
amount: 1000000
}
// 发布订阅
// 订阅传入方法,发布执行队列,以eventName为数组名
var fnList = {}
var eventHub = {
// 发布(事件名称,操作数据)
trigger(eventName, data){
let fnList = fnList[eventName]
if(!fnList){return}
for(let i = 0; i < fnList.length; i++){
fnList[i](data)
}
},
// 订阅(事件名称,操作方法)
on(eventName, fn){
if(!fnList[eventName]){
fnList[eventName] = [];
}
fnList[eventName].push(fn)
}
}
// eventHub.on('我要用钱', function fn1(){})
// eventHub.trigger('我要用钱', 100)
class App extends React.Component{
constructor(){
super()
}
render(){
return (
<div className = "root">
<BigPapa />
<YoungPapa />
</div>
)
}
}
class BigPapa extends React.Component{
constructor(){
super()
this.state = {
money: money
}
}
render(){
return (
<div className = "papa"> 大爸 {this.state.money.amount}
<Son1/>
<Son2/>
</div>
)
}
}
class YoungPapa extends React.Component{
constructor(){
super()
this.state = {
money: money
}
}
render(){
return (
<div className = "papa"> 小爸 {this.state.money.amount}
<Son3/>
<Son4/>
</div>
)
}
}
class Son1 extends React.Component{
constructor(){
super()
this.state = {
money: money
}
}
render(){
return(
<div className="son">儿子1 {this.state.money.amount}
</div>
)
}
}
class Son2 extends React.Component{
constructor(){
super()
this.state = {
money: money
}
}
// 二儿子消费了
x(){
money.amount -= 100
// 二儿子发布花钱事件
eventHub.trigger('我花钱了', 100)
this.setState({
money: money
})
}
render(){
return(
<div className="son">儿子2 {this.state.money.amount}
<button onClick={()=> this.x()}>消费</button>
</div>
)
}
}
class Son3 extends React.Component{
constructor(){
super()
this.state = {
money: money
}
// 三儿子订阅 钱被花了
// 二儿子触发了花钱信息,三儿子监听了花钱信息,如果触发了,就执行
eventHub.on('我花钱了', (data)=>{
this.setState({
money: money
})
})
}
render(){
return(
<div className="son">儿子3 {this.state.money.amount}
</div>
)
}
}
class Son4 extends React.Component{
constructor(){
super()
this.state = {
money: money
}
}
render(){
return(
<div className="son">儿子4 {this.state.money.amount}
</div>
)
}
}
ReactDOM.render(<App/>, document.querySelector('#app'))
3.2.2 单向数据流
- 组件中不可直接修改金额,只有App组件中存money,一路传下去
- 思路:不直接修改金额,提出需求,事件处理中心操作金额,金额组件间传递
- 数据放在顶层,通过事件沟通
// 家族资产总量
var money = {
amount: 1000000
}
// 发布订阅
// 订阅传入方法,发布执行队列,以eventName为数组名
var fnList = {}
var eventHub = {
// 发布(事件名称,操作数据)
trigger(eventName, data){
let fnList = fnList[eventName]
if(!fnList){return}
for(let i = 0; i < fnList.length; i++){
fnList[i](data)
}
},
// 订阅(事件名称,操作方法)
on(eventName, fn){
if(!fnList[eventName]){
fnList[eventName] = [];
}
fnList[eventName].push(fn)
}
}
// 事件处理中心
var x = {
init(){
eventHub.on('我想花钱', function(data){
money.amount -= data
render() // 重新渲染
})
}
}
x.init()
class App extends React.Component{
constructor(){
super()
this.state = {
money: money
}
}
render(){
return (
<div className = "root">
<BigPapa money={this.state.money}/>
<YoungPapa money={this.state.money}/>
</div>
)
}
}
class BigPapa extends React.Component{
constructor(props){
super(props)
}
render(){
return (
<div className = "papa"> 大爸 {this.props.money.amount}
<Son1 money = {this.props.money.amount}/>
<Son2 money = {this.props.money.amount}/>
</div>
)
}
}
class YoungPapa extends React.Component{
constructor(){
super()
}
render(){
return (
<div className = "papa"> 小爸 {this.props.money.amount}
<Son3 money = {this.props.money.amount}/>
<Son4 money = {this.props.money.amount}/>
</div>
)
}
}
class Son1 extends React.Component{
constructor(){
super()
}
render(){
return(
<div className="son">儿子1 {this.props.money.amount}
</div>
)
}
}
class Son2 extends React.Component{
constructor(){
super()
}
// 二儿子提出消费需求
x(){
eventHub.trigger('我想花钱', 100)
}
render(){
return(
<div className="son">儿子2 {this.props.money.amount}
<button onClick={()=> this.x()}>消费</button>
</div>
)
}
}
class Son3 extends React.Component{
constructor(){
super()
}
render(){
return(
<div className="son">儿子3 {this.props.money.amount}
</div>
)
}
}
class Son4 extends React.Component{
constructor(){
super()
}
render(){
return(
<div className="son">儿子4 {this.props.money.amount}
</div>
)
}
}
function render(){
ReactDOM.render(<App/>, document.querySelector('#app'))
}
render()
4. 引入Redux
4.1 名词释义
- store:存储数据的地方
- subscribe:订阅事件
- reducer: 对数据的操作
- action: 触发的事件 action + payload
//================store存所有数据================
// 家族资产总量
var money = {
amount: 1000000
}
// 账户
var user = {
id: 123123,
nickname: '土豪'
}
var store = {
money: money,
user: user
}
//================eventHub================
var fnList = {}
var eventHub = {
trigger(eventName, data){
let fnList = fnList[eventName]
if(!fnList){return}
for(let i = 0; i < fnList.length; i++){
fnList[i](data)
}
},
on(eventName, fn){
if(!fnList[eventName]){
fnList[eventName] = [];
}
fnList[eventName].push(fn)
}
}
// 事件处理中心
var x = {
init(){
// ==================on 是订阅 subscribe================
eventHub.on('我想花钱', function(data){
// =============对数据的变动叫 reducer=================
store.money.amount -= data
render()
})
}
}
x.init()
class App extends React.Component{
constructor(){
super()
this.state = {
store: store
}
}
render(){
return (
<div className = "root">
<BigPapa money={this.state.store.money}/>
<YoungPapa money={this.state.store.money}/>
</div>
)
}
}
class BigPapa extends React.Component{
constructor(props){
super(props)
}
render(){
return (
<div className = "papa"> 大爸 {this.props.money.amount}
<Son1 money = {this.props.money.amount}/>
<Son2 money = {this.props.money.amount}/>
</div>
)
}
}
class YoungPapa extends React.Component{
constructor(){
super()
}
render(){
return (
<div className = "papa"> 小爸 {this.props.money.amount}
<Son3 money = {this.props.money.amount}/>
<Son4 money = {this.props.money.amount}/>
</div>
)
}
}
class Son1 extends React.Component{
constructor(){
super()
}
render(){
return(
<div className="son">儿子1 {this.props.money.amount}
</div>
)
}
}
class Son2 extends React.Component{
constructor(){
super()
}
// 二儿子提出消费需求
x(){
// 事件就是action 我想花钱是type 100是payload 荷载
// ==================action = type + payload==================
eventHub.trigger('我想花钱', 100)
}
render(){
return(
<div className="son">儿子2 {this.props.money.amount}
<button onClick={()=> this.x()}>消费</button>
</div>
)
}
}
class Son3 extends React.Component{
constructor(){
super()
}
render(){
return(
<div className="son">儿子3 {this.props.money.amount}
</div>
)
}
}
class Son4 extends React.Component{
constructor(){
super()
}
render(){
return(
<div className="son">儿子4 {this.props.money.amount}
</div>
)
}
}
function render(){
ReactDOM.render(<App/>, document.querySelector('#app'))
}
render()
4.2 Redux改写
let createStore = Redux.createStore
// state 之前的状态 action 监听的动作 返回新状态
let reducers = (state, action) => {
state = state || {
money: {amount: 100000}
}
switch (action.type) {
case '我想花钱':
return {
money: {
amount: state.money.amount - action.payload
}
}
case 'event2':
break;
case 'event3':
break;
default:
return state
}
}
const store = createStore(reducers)
class App extends React.Component {
constructor(){
super()
}
render(){
return (
<div className="root">
<BigPapa money={this.props.store.money} />
<YoungPapa money={this.props.store.money}/>
</div>
)
}
}
class BigPapa extends React.Component{
constructor(){
super()
}
render(){
return (
<div className="papa"> 大爸 {this.props.money.amount}
<Son1 money={this.props.money}/>
<Son2 money={this.props.money}/>
</div>
)
}
}
class YoungPapa extends React.Component{
constructor(){
super()
}
render(){
return (
<div className="papa"> 二爸{this.props.money.amount}
<Son3 money={this.props.money}/>
<Son4 money={this.props.money}/>
</div>
)
}
}
class Son1 extends React.Component{
constructor(){
super()
}
render(){
return (
<div className="son"> 儿子1 {this.props.money.amount}</div>
)
}
}
class Son2 extends React.Component{
constructor(){
super()
}
x(){
store.dispatch({ type: '我想花钱', payload: 100 })
}
render(){
return (
<div className="son"> 儿子2 {this.props.money.amount}
<button onClick={()=>this.x()}>消费</button>
</div>
)
}
}
class Son3 extends React.Component{
constructor(){
super()
}
render(){
return (
<div className="son"> 儿子3 {this.props.money.amount}</div>
)
}
}
class Son4 extends React.Component{
constructor(){
super()
}
render(){
return (
<div className="son"> 儿子4 {this.props.money.amount}</div>
)
}
}
function render(){
ReactDOM.render(<App store={store.getState()}/>, document.querySelector('#app'))
}
// 最后订阅发布
render()
store.subscribe(function(){
render()
})
5. Redux详解
5.1 Redux in VanilliJS
- 例子: jsbin.com/ribumunuhu/…
- 缺点:整体更新
// 1.创建一个全局store
var store = Redux.createStore(更新store的函数-counter)
// 2. 操作store的变化,根据操作生成新的state
function counter(之前的状态-state, 操作-action){
return 新状态-newState
}
// 3.渲染
render(store)
// 4.页面获取 store.getState()
// store存储state,获取
<span id="value">${store.getState()}</span>
// 5.函数事件中触发变化,更新
// 触发一个事件,派发一个动作,dispatch一个action
store.dispatch({type:'add', payload: 1})
// 6.监听变化重新渲染
store.subscribe(()=>{
render(store)
})
5.2 React + Redux
- dom diff更新
-
可不可以统一派发?
-
但是还得store一层一层往下传
5.2 React + Redux + React-Redux
- index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { createStore } from 'redux'
import { Provider } from "react-redux";
const reducer = (state, action)=>{
if(state === undefined){
return {n: 0}
}else{
if(action.type === 'add'){
var newState = {n: state.n + action.payload}
return newState
}else{
return state
}
}
}
const store = createStore(reducer)
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
- App.js
import React, { Component } from 'react';
import { connect } from "react-redux";
class App extends Component {
render() {
return (
<div>
你点击了 <span id="value">{this.props.n}</span> 次
<div>
<button id="add1" onClick={()=> this.props.add1()}>+1</button>
<button id="add2">+2</button>
<button id="add1IfOdd">如果是单数就+1</button>
<button id="add1After2Sec">两秒钟后+1</button>
</div>
</div>
);
}
}
// 把全局 state 映射给 当前组件的 props
function mapStateToProps(state){
return {
n: state.n
}
}
// 接收一个 Dispatch 映射给 Props 的 add1
function mapDispatchToProps(dispatch) {
return {
add1: ()=> dispatch({type:'add', payload: 1})
}
}
// 导出 connect 之后的 App
export default connect(mapStateToProps, mapDispatchToProps)(App);
6. Context API
6.1 JS以及React的多层传参
- 缺点:值必须逐层传递,不能断层,如果放全局,又会被修改的风险
6.1.1 JS 中
console.log(1, n1);
f2(n1);
}
function f2(n2) {
console.log(2, n2);
f3(n2);
}
function f3(n3) {
console.log(3, n3);
f4(n3);
}
function f4(n4) {
console.log(4, n4);
}
{
let n = 100;
f1(n);
console.log("done");
}
6.1.2 React 中
import React from "react";
import "./styles.css";
function F1(props) {
return (
<div>
111,{props.n1}
<F2 n2={props.n1}></F2>
</div>
);
}
function F2(props) {
return (
<div>
222,{props.n2}
<F3 n3={props.n2}></F3>
</div>
);
}
function F3(props) {
return (
<div>
333,{props.n3}
<F4 n4={props.n3}></F4>
</div>
);
}
function F4(props) {
return <div>444,{props.n4}</div>;
}
export default class App extends React.Component {
constructor() {
super();
this.state = {
n: 99
};
}
render() {
return (
<div>
<F1 n1={this.state.n}></F1>
</div>
);
}
}
6.2 用Context传值(局部的全局变量)
6.2.1 JS demo
{
let context = {};
window.setContext = function(key, value) {
context[key] = value;
};
window.f1 = function f1() {
console.log(1);
f2();
};
function f2() {
console.log(2);
f3();
}
function f3() {
console.log(3);
f4();
}
// 不需要逐级传递,局部访问的全局变量
function f4() {
console.log(4, context["n"]);
}
}
{
window.setContext("n", 100);
setTimeout(() => {
window.f1();
}, 1000);
console.log("done");
}
6.2.2 React 中
-
创建一个Context: const nContext = React.createContext();
-
提供:<nContext.Provider value={this.state.x}></nContext.Provider>
-
使用:<nContext.Consumer></nContext.Consumer>
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function F1(props) {
return (
<div className="bordered">
1111
<F2 />
</div>
);
}
function F2(props) {
return (
<div className="bordered">
2222
<F3 />
</div>
);
}
function F3(props) {
return (
<div className="bordered">
3333
<nContext.Consumer>
{x => <F4 n4={x.n} setN={x.setN} />}
</nContext.Consumer>
</div>
);
}
function F4(props) {
return (
<div className="bordered">
4444, {props.n4}
<button onClick={props.setN}>Click me</button>
</div>
);
}
const nContext = React.createContext();
class App extends React.Component {
constructor() {
super();
this.state = {
x: {
n: 67,
setN: () => {
this.setState({
x: {
...this.state.x,
n: this.state.x.n + 1
}
});
console.log("执行完毕");
}
}
};
}
render() {
return (
<div>
<nContext.Provider value={this.state.x}>
<F1 />
</nContext.Provider>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);