React

react笔记

脚手架环境:全局安装 npm i create-react-app -g

进入目录create-react-app 项目名称

create-react-app --version

jsx原理

跟vue一样,只能写在一个标签里类似vue用template

//jsx原理是个语法糖,利用createElement创建节点,节点名称,属性,内容
var a = React.createElement('h2',null,'hello react')
ReactDOM.render(a,document.getElementById('box'))

var a = <h2>hello react</h2>
ReactDOM.render(a,document.getElementById('box'))

在react里写类名用className=""

        function fn() {
            var time = Date.now()
            ReactDOM.render(<div className="oon">
                                {new Date(time).toLocaleString()}                
    </div>,
                            document.getElementById('box'));
        }
        setInterval(() => {
            fn()
        }, 1000);

在react里,{插值表达式} 里不能直接渲染对象,只能直接渲染数组

但是渲染数组时,必须要给每个元素指定给一个key值 (保证唯一性)

遍历对象

把对象转换为数组

Object.keys(对象) 返回对象的所有的键构成的数组

Object.value(对象) 返回对象的所有的值构成的数组

Object.entries(对象) 返回对象所有的键值对,所构成的数组

var obj={a:1,b:2,c:3,d:4,e:5} 
ReactDOM.render(<div>
                <ul>
                {
    Object.keys(obj).map((item)=>{
    return <li key={item}>{item}:{obj[item]}</li>
})
}
    </ul>
</div>,
document.querySelector("#box"));

脚手架环境引用图片

方法1:
import 变量 from "图片路径"


方法2:
<img src={require("图片的路径")} />

验证属性

下载插件npm install --save pro-types 不用下载安装就可以用

父组件内容同上,只是把n传过来了

//子组件 引用import PropTypes from 'prop-types';
import PropTypes from 'prop-types';  //验证属性的包

// 验证属性:n必须是number,而且必须传
static propTypes = {
    n: PropTypes.number.isRequired
}
static propTypes ={
  key:PropTypes.类型.[isRequired]
}

基本用法

//todolist.js
import React, { Component,Fragment } from 'react'
import Todoitem from '../todoitem'

export default class todolist extends Component {
    constructor(props) {
        super()
        this.state = {
            listData:[]
        }
    }
    render() {
        return (
            //最外层跟vue一样必须只能有一个元素,但是如果不需要这个元素可以直接用<></>代替最外层
            //或者用Fragment
            <>
                <ul>
                    {
                        //循环必须要加key值
                        this.state.listData.map((item,index)=>{
                            //父传子数据singleItem
                            //父传子方法getdata,deldata
                            return <Todoitem key={index} singleItem={item} getdata={this.changeState.bind(this)} deldata={this.delState.bind(this)}/>
                        })
                    }
                </ul>
            </>
        )
    }
    componentDidMount() {
        this.iniData()
    }
    changeState(id){
        let arr = [...this.state.listData]
        arr.map(item=>{
            item.id == id ? item.isComplete = !item.isComplete : console.log(item)
        })
        console.log(arr)
        this.setState({
            listData : arr
        })
    }
    delState(id){
        //深复制方便修改数据
        //展开运算符能深复制2层以内的数据,复杂的数据结构可以用immutable扩展包
        let arr = [...this.state.listData]
        let newarr = arr.filter(item=>{
            return item.id!=id
        })
        //react无法直接修改state里面的数据
        //只能通过setState修改
        this.setState({
            listData : newarr
        })
    }
    async iniData(){
        await fetch('http://rap2api.taobao.org/app/mock/data/1877922')
                .then(resp => resp.json())
                .then(json => {
                    this.setState({
                        listData:json.lists
                    })
                })
        console.log(this.state.listData)
    }
}

//Todoitem.js
import React, { Component } from 'react'
export default class todoitem extends Component {
    render() {
        return (
            <div>
                {/* this.props.singleItem为父组件传过来的数据 */}
                <div onClick={()=>this.changeState(this.props.singleItem.id)}>{this.props.singleItem.name}-{this.props.singleItem.isComplete ? '已完成' : '未完成'}</div>
                <button onClick={()=>{this.delData(this.props.singleItem.id)}}>Del</button>
                <div  style={{display: true ? "block" : "none"}} ref="getref" onClick={this.handleGetRef}>动态绑定style</div>
                <input type="text" ref={(node)=>this.username=node} />

            </div> 
        )
    }
    delData=(id)=>{
        //直接调用父组件的方法
        this.props.deldata(id)
    }
    changeState=(id)=>{
        this.props.getdata(id)
    }
    //通过ref拿到当前元素
    handleGetRef=()=>{
        //方法1
        console.log(this.refs.getref)
        //方法2
        //ref={(当前节点的变量)=>this.当前实例的变量=当前节点的变量}
        //通过this.当前实例变量就能找到,例:this.username.value
        //this.当前实例的变量  就找到了这个结节
        var username = this.username.value;
    }
}

生命周期

旧生命周期

初始:

componentWillMount(服务端渲染会重复执行有隐患):render之前最后一次修改状态机会

render:只能访问props,state,不允许修改state

componentDidMount(一般在这里执行ajax,绑定setInterval):Dom渲染完成过后

constructor:constructor()中完成了React数据的初始化,它接受两个参数:props和context,当想在函数内部使用这两个参数时,需使用super()传入这两个参数。
注意:只要使用了constructor()就必须写super(),否则会导致this指向错误。

更新:

componentWillReceiveProps:父组件修改属性触发

shouldComponentUpdate(对比state,无变化则组织render更新):返回false会组织render 调用,性能调优函数,可以获取新老状态

shouldComponentUpdate(nextProps,nextState){  if(this.state.myname!== nextProps.myname){    return true  }  return false}

render:~

componeDidUpdate:可以修改Dom

销毁:

componentWillUnMount(在这里清除定时器):组件销毁前触发

新生命周期

getDerivedStateFromProps:第一次初始化组件以及后续的更新过程中(包括自身状态更新以及父传子),返回一个对象作为新的state,返回null则说明不需要在这里更新state

初始化会执行,每次更新也会执行

PS:无法做异步请求,要做异步必须配合componeDidUpdate结合state来发ajax请求

static getDerivedStateFromProps(nextProps, prevState) {    //这里可解决频繁发送请求的问题,多次请求只会抛最后一次请求  if (nextProps.isLogin !== prevState.isLogin) {    return {      isLogin: nextProps.isLogin,    };  }  return null;}componentDidUpdate(prevProps, prevState) {  if (!prevState.isLogin && this.props.isLogin) {    this.handleClose();  }}

getSnapshotBeforeUpdate(prevProps, prevState)---解决拿到状态时间过久的问题

getSnapshotBeforeUpdate(){  //获取滚动条位置  return{    y:100  }}componentDidUpdate(preProps,preState,data){  //data为getSnapshotBeforeUpdate返回的数据,{y:100}}

代替componentWillUpdate。
常见的 componentWillUpdate 的用例是在组件更新前,读取当前某个 DOM 元素的状态,并在 componentDidUpdate 中进行相应的处理。
这两者的区别在于:

  1. 在 React 开启异步渲染模式后,在 render 阶段读取到的 DOM 元素状态并不总是和 commit 阶段相同,这就导致在
    componentDidUpdate 中使用 componentWillUpdate 中读取到的 DOM 元素状态是不安全的,因为这时的值很有可能已经失效了。
  2. getSnapshotBeforeUpdate 会在最终的 render 之前被调用,也就是说在 getSnapshotBeforeUpdate 中读取到的 DOM 元素状态是可以保证与 componentDidUpdate 中一致的。
    此生命周期返回的任何值都将作为参数传递给componentDidUpdate()。

pureComponent可以解决组件重复更新的问题

import React,{PureComponent} from 'react'
export default class App extends PureComponent {
  //原理是scu内部一直在对比(shallow equal,return ture)
}

用于优化页面性能:

shouldComponentUpdate(nextProps,nextState){} 第一个参数是最新的接收的数据,第二个参数是最新的数据

返回布尔值 :true视图就会render,false视图不会渲染

可以减少不必要的渲染,增加页面的性能

export default class Item extends Component {
    shouldComponentUpdate(nextProps,nextState){
        //判断,当里面的数据和新改变的数据不相等时才渲染render
        return this.props.completed !== nextProps.completed; 
    }
    render(){
        console.log("item render");
        let {completed,id,task,changeComplete,remove} = this.props;
        return <li>
                    <input type="checkbox" defaultChecked={completed} onChange={changeComplete.bind(this,id)}/>
                                    {task}
                     <button onClick={remove.bind(this,id)}>删除</button> 
              </li>
    }
}

componentDidMount(第一个参数是上一次的state数据,) 类似vue里的mounted ,在组件挂在后....获取节点/发送请求等 这个钩子能放访问this,所以在这调用方法时,不用再绑定this了

render 渲染界面

static getDerivedStateFromProps(props,state) 更新派生属性:第一个参数是props最新数据,此方法不能用this

 派生状态:当组件的state的值 如果 来自于props 就叫派生状态
(获取派生状态来自于props),加了static就是静态方法,
在面向对象里 类不用实例化就可以直接使用这个类的方法,例:Date.now()
静态方法里不能用this,因为没有实例化
static getDerivedStateFromProps(props, state) {  //更新派生状态
    return {
        a:props.n
    }
}

当组件实例被创建并插入DOM中时(挂载阶段),生命周期的钩子函数执行顺序:

constructor()
static getDerivedStateFromProps() 
render()
componentDidMount() //永远在最后执行
one 组件是test的子组件
test constructor
test.js

test render
one.js

one constructor
one.js

one render
one.js

one componentDidMount
test componentDidMount

更新阶段的钩子函数

getSnapshotBeforeUpdate(prevProps,prevState) 更新之前,返回当前dom状态给componentDidUpdate的第三个参数,如果用了此钩子函数那么必须写下面的钩子函数

componentDidUpdate(prevProps,prevState,snapshot) 更新发生后立即调用。初始渲染不会调用此方法。监听数据的变化

卸载

componentWillUnmount()从DOM中删除组件时调用此方法,//清理资源,防止内存泄露

经常出现一个bug,异步请求后,卸载组件,组件卸载结束后异步数据接收过来时

index.js:1375 Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
    in Two (at App.js:14)
    in div (at App.js:13)
    in App (at src/index.js:7)

然后在componentWillUnmount里面写

//重写setState    
this.setState = (state,callback)=>{ return;};

路由

需要下载插件 npm install react-router-dom

分为history模式 BrowserRouter ==>URL不加#

history这个模式需要后端配置好才能用

hash 模式 HashRouter ===>URL加#

现在react-router-dom版本6.0变化

  • Switch -》Routes
  • component={Login} -》 element={<Login />}
//route/index.js
import {
    HashRouter as Router,
    Redirect,
    Route,
    Switch
} from 'react-router-dom'
//Login Dashboard NotFound为组件
import NotFound from '../error'
import Login from '../pages/login'
import Dashboard from '../pages/dashboard'



const BlogRouter = ()=> {
    return <Router>
        <Switch>
            {/*Redirect为重定向,to为定的路由路径*/}
            <Route path='/login' component={Login} />
            {
                localStorage.getItem('token') ?
                <Route path='/' component={Dashboard} /> :
                <Redirect to="/login" />
            }
            {/*exact表示精准定位*/}
            <Redirect from="/" to="/home" exact/>
            {/*路由的匹配方式为从上往下匹配,匹配到相应的path就会直接跳转,防止路径出错,需要用*来匹配所有路径引导至404资源丢失页面*/}
            <Route path="*" component ={NotFound}/>
        </Switch>
    </Router>
}
export default BlogRouter

路由传参

路由三大对象

  • history -- 路由跳转掉用
  • location -- 读取query传的参数
  • match -- 读取Url地址参数

1.params

<Route path='/path/:name' component={Path}/>
<NavLink to="/path/2">xxx</NavLink>
//如果要传多个就继续/:id/:xx...
this.props.history.push({pathname:"/path/" + name});
读取参数用:this.props.match.params.name

优势 : 刷新地址栏,参数依然存在
缺点:只能传字符串,并且,如果传的值太多的话,url会变得长而丑陋。

2.query

<Route path='/query' component={Query}/>
<NavLink to={{ path : ' /query' , query : { name : 'sunny' }}}/>
this.props.history.push({pathname:"/query",query: { name : 'sunny' }});
读取参数用: this.props.location.query.name

优势:传参优雅,传递参数可传对象;
缺点:刷新地址栏,参数丢失

3.state

<Route path='/sort ' component={Sort}/>
<NavLink to={{ path : ' /sort ' , state : { name : 'sunny' }}}/> 
this.props.history.push({pathname:"/sort ",state : { name : 'sunny' }});
读取参数用: this.props.location.query.state 

优缺点同query

4.search

<Route path='/web/departManange ' component={DepartManange}/>
<NavLink to="web/departManange?tenantId=12121212">xxx</NavLink>
this.props.history.push({pathname:"/web/departManange?tenantId" + row.tenantId});
读取参数用: this.props.location.search

优缺点同params

路由高阶组件withrouter

由于子组件没有history方法,在里面获取不到props参数

//这里当做props传入
<SideMenu collapsed={this.state.collapsed} airhistory={this.props.history}/>

第二种方法用withrouter高阶组件传入history方法

//高阶组件withRouter 获取低阶组件,生成高阶组件
import { withRouter } from 'react-router' //路由
class topHeader extends Component {
    rednder(){
        return {
            this.props.collapsed
        }
    }
    quiet=()=>{
        this.props.history.push('/login')
    }
}
export default withRouter(topHeader)

权限路由

//App.js
import React from 'react';
import './App.css';
import {Route,Redirect,Switch} from 'react-router-dom'
import {subRoutes} from './router' //引入subroutes数据
import Admin from './components/layout'; //引入布局
import Myroute from './Myroute'
function App() {
  return (
    <div className="App">
       <Admin>
          <Switch>
          {
              subRoutes.map((item)=>{
      //当进入页面时,跳转到自定义高阶组件Myroute中进行操作
                return <Myroute path={item.path} key={item.path} component={item.component} roles={item.roles} />
              })
          }
          <Redirect from="/home" to="/home/list" exact />
          </Switch>
       </Admin>
    </div>
  );
}

export default App;
//myroute.js
import React, { Component } from 'react'
import {Route,Redirect} from 'react-router-dom'
export default class Myroute extends Component {
    render() {
        let {path,component:Com,roles} = this.props
        // 当进入一个页面时,进行遍历当前的用户是否在router里的权限数组里,如果有就true,否则为false
        var hasPermission = roles.some((item)=>item===sessionStorage.getItem('user')) 
        return <Route path={path} render={
            (props)=>{
                // 如果为true则渲染当前页面,如果为false则弹出没有权限
                return hasPermission ? <Com {...props} /> :<div>没有权限访问该页面</div>
            }
        } />
    }
}
**router/index.js
export const subRoutes =[
    {
        path:'/home/dashboard',
        component:DashBoard,
        roles:['zhm','abc']
    },
    {
        path:'/home/list',
        component:List,
        roles:['zhm','abc']
    },
    {
        path:'/home/setting',
        component:Setting,
        roles:['zhm']

    },
    {
        path:"/home/add",
        component:Add,
        roles:['zhm','abc']
    },{
        path:'/home/notify',
        component:Notify,
        roles:['zhm','abc']
    }
]

redux

状态管理工具,各个组件可以访问公共数据

各个组件都需要用的信息都可以存到redux里,类似vuex

npm install redux

单向数据流

store 仓库是唯一的

atate 是只读的,修改需要拷贝副本

使用纯函数来执行修改

store文件夹

//index.js
import {createStore} from "redux";
import reducer from './reducer';  //专门用于修改状态的纯函数
var store = createStore(reducer); // 传入ruducer 创建一个store

export default store;
//reducer.js
//初始数据对象
var initState ={ 
    n:1
}
//创建一个纯函数,有两个参数,第一个为数据,第二个为加工数据,
//专门用于修改状态的纯函数
var reducer = (state=initState,action)=>{  
    return state;
}

export default reducer;

取出数据

store.getState()
import React, { Component } from 'react'
import store from '../store';
export default class One extends Component {
  constructor(props){
      super(props);
      this.state ={
          n:store.getState().n
      }
  }
  render() {
      let {n} = this.state;
    return (
      <div>
            {n}
      </div>
    )
  }
}

通过context获取store数据

可以从顶层组件, 进行跨组件传值

就不用在每个页面引入store,

引入store文件夹后

//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import {MyProvider} from "./components/MyProvider"
//引入store,并以参数的形式传给App
import store from './store'
ReactDOM.render(
     <MyProvider store={store}>
        <App />
    </MyProvider>, document.getElementById('root'));
serviceWorker.unregister();
//MyProvider.js
import React, { Component,createContext } from 'react'
//createContext有两个参数,第一个是Provider,提供数据,
//第二个参数是Consumer, 接受数据。
const ctx = React.createContext()

 class MyProvider extends Component {
  render() {
    console.log(this.props.a)
    return (
      <div>
        //接受App传的参数,即store
          <ctx.Provider value={this.props.store}>
            //渲染App
              {this.props.children}
          </ctx.Provider>
      </div>
    )
  }
}

export {MyProvider,ctx};//导出模块和context, 在App.js里进行包裹和传参

方法1:

//One.js
import React, { Component } from 'react'

import Two from './Two'
import {ctx} from './MyProvider';
export default class One extends Component {
    //固定写法
    static contextType= ctx 
    constructor(props,context){
        super(props,context);
        this.state={
            //context相当于store
            n:context.getState().n //就通过context来获取,而不是store
        }
    }
  render() {
    return (
      <div>
          one
            {this.state.n}
         <Two />
      </div>
    )
  }
}

方法2:

//two.js
import React, { Component } from 'react'
import {ctx} from "./MyProvider"

export default class Two extends Component {
    static contextType = ctx;
  render() {
     
    return (
      <div>
        {this.context.getState().n}
         <ctx.Consumer>
             {
                 (value)=>{
                    return <div>{value}</div>
                 }
             }
         </ctx.Consumer>
      </div>
    )
  }
}

##修改数据

在组件里定义方法,发出动作

store.dispatch()

inc(){
    //actionCreator.incActio 引入的actioncreator文件里面的incActio方法并传参
        store.dispatch(actionCreator.incAction(5))// 发出动作给reducer ,动作对象必须有type属性
    }
<button onClick={this.inc}>+</button>

在reducer里面判断type,先浅拷贝一份副本,改变副本状态,返回新状态

var newState ={...state}; //副本
if(action.type==="INCREMENT"){
    // return {...state,n:state.n+action.p} 简写
    newState.n+=action.p; //修改副本的数据
    return newState; //返回新状态
}

数据改变后,视图不会刷新 store.subscribe(callback) //监控store 里数据的变化,数据变了,回调函数会执行 重新从store取数据,更新state,触发渲染视图

//在每个需要刷新的页面地方写
constructor(props){
    super(props);
    this.state = {
        n:store.getState().n //获取n
    }
    store.subscribe(this.change.bind(this)) //监控store里的数据变化,一旦变化了,就执行回调
}
change(){
    this.setState({
        n:store.getState().n //数据变化后立即把store的n重新赋值给n
    })
}

例:

//reducer.js
//保存数据在reducer里
var initState  ={
    n:1,
    list:[
        {
            "id":1,
            "title":'aa'
        },
        {
            "id":2,
            "title":'bb'
        },
        
    ],
    genId:2
}

export default (state=initState,action)=>{
    var newState ={...state}; //副本
    if(action.type==="INCREMENT"){
        // return {...state,n:state.n+action.p}
        newState.n+=action.p; //修改副本的数据
        return newState; //返回新状态
    }
    //加
    else if(action.type==="ADD"){
        newState.list.push({id:++newState.genId,title:action.title})
        return newState;
    }
    return state;
}
//two.js
import React, { Component } from 'react'
import store from '../../store'
import actionCreator from './actionCreator' //引入方法
export default class Two extends Component {
    constructor(props){
        super(props);
        this.state = {
            n:store.getState().n
        }
        store.subscribe(this.change.bind(this)) //监控store里的数据变化,一旦变化了,就执行回调
    }
    //数据变化后立即把store的n重新赋值给n
    change(){
        this.setState({
            n:store.getState().n
        })
    }
    inc(){
        store.dispatch(actionCreator.incAction(5))// 发出动作给reducer ,动作对象必须有type属性
    }
  render() {
    return (
      <div>
        {this.state.n}
        <button onClick={this.inc}>+</button>//点击加号增长n
      </div>
    )
  }
}
//actionCreator.js
export default {
    //接受参数5,并传给reducer.js
    incAction(p){
        return {
            type:'INCREMENT',
            p
        }
    }
}
//one.js
import React, { Component } from 'react'
import Two from '../two'
import store from '../../store';
import actionCreator from './actionCreator'
export default class One extends Component {
  
    constructor(props,context){
        super(props,context);
        this.state={
            n:store.getState().n,
            list:store.getState().list
        }
        store.subscribe(this.change.bind(this)) //监控store里的数据变化,一旦变化了,就执行回调
    }
    change(){
        this.setState({
            n:store.getState().n,
            list:store.getState().list
        })
    }
    add(e){
        if(e.keyCode===13){
             store.dispatch(actionCreator.addAction(e.target.value)); 
             e.target.value=""  //清空文本框
        }
    }
  render() {
    return (
      <div>
            {this.state.n}
            <input type="text" onKeyUp={this.add} />
            <ul>
                {
                    this.state.list.map((item)=>{
                        return <li key={item.id}>{item.title}</li>
                    })
                }
            </ul>
         <Two />
      </div>
    )
  }
}
//actionCreator.js
export default {
    addAction(title){
        return {
            type:'ADD',  //type 值不能重复  必须的
            title
        }
    }
}

模块化

//store取数据
store.getState().变量
//context取数据
context.getState().模块名.变量

//定义方法,发出动作,用了context则更改为this.context
//调用引入的actionCreator文件里的removeAction方法
store.dispatch(actionCreator.removeAction())

//监听数据变化,用context后把store更改为store即可
store.subscribe(this.定义的方法名.bind(this)) //监听数据变化
**在components创建一个MyProvider
//此文件作用是,可以从顶层向底层传参
//前两行固定语法
import React, { Component, createContext } from 'react'
const context = createContext()
class MyProvider extends Component {
    render() {
        return (
            <div>
              //在index.js里包裹了App组件,所以接收用this.props
                <context.Provider value={this.props.store} >
                    {this.props.children}    
                </context.Provider>              
            </div>
        )
    }
}
//在index.js用MyProvider去包裹App组件,再在每个页面的index.js里引入context
export { MyProvider, context }
**index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
//引入所有store数据
import store from './store'  
import {MyProvider} from './components/MyProvider'

ReactDOM.render(
                <MyProvider store={store}>
                    <App />
                </MyProvider>, document.getElementById('root'));
serviceWorker.unregister();
**store/index.js
//固定语法
import {createStore} from "redux";
import reducer from './reducer';  //专门用于修改状态的纯函数
var store = createStore(reducer); // 传入ruducer 创建一个store

export default store;
**store/actionType.js
//保存名称统一管理,只要用到ADD名称的 都可以引入此文件
export const ADD = 'ADD'
export const REMOVE = 'REMOVE'
**store/reducer.js
//redux自带combineReducers,可以把所有的reducer合并为一个
import {combineReducers} from 'redux'
import todoReducer from '../components/one/reducer' //引入one的reducer,里面写的方法和数据
var reducer = combineReducers({
    //模块名:引入文件的名称
    one:todoReducer 
})
export default reducer

做的添加和删除功能

**one/index.js
import React, { Component } from 'react'
import { context } from '../MyProvider'//引入context获取store数据
import actionCreator from './actionCreator' 

class OneRender extends Component {     //UI组件
    render() {
        return <div>
                    <input type="text" onKeyUp={this.props.add} />
                    <ul>
                        {
                            // 因为是接受父组件的参数方法等,所以用this.props接受
                            this.props.list.map(item => {
                                return <li key={item.id}>
                                    {item.title}
                                    <button onClick={this.props.remove.bind(this, item.id)}>删除</button>
                                </li>
                            })
                        }
                    </ul>
                </div>
    }
}
export default class One extends Component {    //容器组件
    // 固定写法
    static contextType = context;
    // 除了constructor以外,其他地方调用context都要加this
    constructor(props, context) {
        super(props, context);
        this.state = {
            // 用context之前:store.getState().变量
            //用了context后用:
            //context.getState().模块名(此模块名是在store/reducer.js定义的).变量
            list: context.getState().one.list,
        }
        this.add = this.add.bind(this)
        this.remove = this.remove.bind(this)
        context.subscribe(this.change.bind(this)) //监听数据变化
    }
    change() {
        this.setState({
            list: this.context.getState().one.list
        })
    }
    add(e) {
        if (e.keyCode === 13) {
            this.context.dispatch(actionCreator.addAction(e.target.value));
            e.target.value = ""  //清空文本框
        }
    }
    remove(id) {
        this.context.dispatch(actionCreator.removeAction(id))
    }
    render() {
        return (
            <div>
                {/* 给子组件传方法 */}
                <OneRender add={this.add} remove={this.remove} list={this.state.list} />
            </div>
        )
    }
}
**one/actionCreator.js
// 引入保存的名称,那边改了这边跟着自动更改
import { ADD, REMOVE } from '../../store/actionType'
export default {
    addAction(title){
        return {
            type:ADD,  //type 值不能重复  必须的
            title
        }
    },
    removeAction(id){
        return{
            type:REMOVE,
            id
        }
    }
}
**one/reducer.js
// 此文件是,在每个文件夹里写单独的数据和方法,在store里的reduce引入此文件
// 变成:one:todoReducer ,所以可以通过 context.getState().one.list获取到数据
//引入store的固定变量,在store里改了则这边就改变了
import {ADD,REMOVE} from '../../store/actionType'
var initState  ={
    list:[
        {
            "id":1,
            "title":'aa'
        },
        {
            "id":2,
            "title":'bb'
        },
    ],
    genId:2
}

export default (state=initState,action)=>{
    var newState ={...state}; //副本
    if(action.type===ADD){
        newState.list.push({id:++newState.genId,title:action.title})
        return newState;
    }
    else if(action.type===REMOVE){
        newState.list=newState.list.filter(item=>{
            return item.id !== action.id
        })
        return newState
    }
    return state;
}

react-redux

npm i react-redux

优化上面的代码,不需要容器组件,只需要UI组件

在index.js引入
import {Provider} from 'react-redux' 
包裹App组件
在需要的子组件index.js引入
import {connect} from 'react-redux'
在尾部导出组件
//第一个参数是数据,括号里是仓库的数据(即reducer.js的数据),映射为state,第二个参数是所有的方法,后面的括号里写此类组件的名字
export default connect(mapState,mapDispatch)(OneRender)
**index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import store from './store'
//引入下载的插件,就能拿到所有的store数据
import {Provider} from 'react-redux' 

ReactDOM.render(
                <Provider store={store}>
                    <App />
                </Provider>, document.getElementById('root'));
serviceWorker.unregister();

**one/index.js
import React, { Component } from 'react'
import actionCreator from './actionCreator' 
import {connect} from 'react-redux'
class OneRender extends Component {     //UI组件
    add=(e)=>{
        if(e.keyCode===13){
            this.props.addAction(e.target.value)
            e.target.value=''
        }
    }
    render() {
        console.log(this.props) //可以查看到所有方法和数据  
        return <div>
                    {/* 因为需要判断,所以需要先写个方法去判断,判断成功后再通过this.props.addAction去调用传值 */}
                    <input type="text" onKeyUp={this.add} /> 
                    <ul>
                        {
                            // 因为是接受父组件的参数方法等,所以用this.props接收,加上模块名称(此模块名是在store/reducer.js定义的).变量
                            this.props.one.list.map(item => {
                                return <li key={item.id}>
                                    {item.title}
                                    {/* 方法名是在reducer里写的名字 */}
                                    <button onClick={this.props.removeAction.bind(this, item.id)}>删除</button>
                                </li>
                            })
                        }
                    </ul>
                </div>
    }
}
//第一个参数是数据,括号里是仓库的数据,映射为state,第二个参数是所有的方法,后面的括号里写此类组件的名字
export default connect((state)=>state,actionCreator)(OneRender)
**one/index.js
//映射
import React, { Component } from 'react'
import actionCreator from './actionCreator' 
import {connect} from 'react-redux'
import { taggedTemplateExpression } from '@babel/types'
class OneRender extends Component {     //UI组件
    add=(e)=>{
        if(e.keyCode===13){
            this.props.add(e.target.value)
            e.target.value=''
        }
    }
    render() {
        return <div>
                    {/* 因为需要判断,所以需要先写个方法去判断,判断成功后再通过this.props.addAction去调用传值 */}
                    <input type="text" onKeyUp={this.add} /> 
                    <ul>
                        {
                            // 因为是接受父组件的参数方法等,所以用this.props接收,加上模块名称(此模块名是在store/reducer.js定义的).变量
                            this.props.list.map(item => {
                                return <li key={item.id}>
                                    {item.title}
                                    {/* 方法名是在reducer里写的名字 */}
                                    <button onClick={this.props.remove.bind(this, item.id)}>删除</button>
                                </li>
                            })
                        }
                    </ul>
                </div>
    }
}
var mapState=(state)=>{
    return {
        // 实际上是变化了,并没有监控到数据变化
        list:state.one.list,
        //所以去主动监控一个数据随时变化的值,不需要去渲染
        length:state.one.list.length
    }
}
var mapDispatch =(dispatch)=>{
    return {
        remove(id){
            dispatch(actionCreator.removeAction(id))
        },
        add(text){
            dispatch(actionCreator.addAction(text))
        }
    }
}
//第一个参数是数据,括号里是仓库的数据(即reducer.js的数据),映射为state,第二个参数是所有的方法,后面的括号里写此类组件的名字
export default connect(mapState,mapDispatch)(OneRender)

异步请求

npm i redux-thunk

等异步请求后,再发出动作

**store/index.js
//引入
import thunk from 'redux-thunk'
import {createStore,applyMiddleware} from 'redux';
import reducer from './reducer';  //总的reducer 

var store =  createStore(reducer,applyMiddleware(thunk));

export default store;
**list/index.js
import React, { Component } from 'react'
import {connect} from 'react-redux'
import actionCreator from './actionCreator'
 class List extends Component {
  componentDidMount(){
      //组件挂载后发送请求
      this.props.getData();
  }
  render() {
     console.log(this.props)
    return (
      <div>
            <ul>
                {//判断,如果有再去遍历
                   this.props.list.list.data &&  this.props.list.list.data.map((item)=>{
                        return <li key={item.id}>{item.title}</li>
                    })
                }
            </ul>
      </div>
    )
  }
}

export default connect((state)=>state,actionCreator)(List)
**list/actionCreator.js
//引入axios和写方法
import axios from 'axios';

//导出各种方法,并返回一个回调动作给到reducer.js
export default {
    getData(){
        return (dispatch)=>{
              axios.get("http://jsonplaceholder.typicode.com/posts").then((res)=>{
                  dispatch({
                      type:'GETDATA',
                      list:res //把请求到的数据传给
                  })
              })
        }
    }
}
**list/reducer.js
//写数据
var initState ={
    list:[]
}

 const reducer= (state = initState, action) => {
    switch (action.type) {
        case 'GETDATA':
            var newState ={...state};
            newState.list =action.list;
            return newState
        default:
            return state
    }
}

export default reducer;
**store/reducer.js
//redux自带combineReducers,可以把所有的reducer合并为一个
import {combineReducers} from 'redux'
//引入list 的reducer,里面写的方法和数据,这样才可以在list.index.js里通过this.props.list得到数据
import listReducer from '../components/list/reducer'
var reducer = combineReducers({
    //模块名:引入文件的名称
    one:todoReducer ,
    list:listReducer
})
export default reducer

懒加载组件

yarn add ract-loadble

import React from 'react'
import Loadable from 'react-loadable';
//进入home路径才加载home
const Home = Loadable({
    loader: () => import('../App'),
    loading: ()=><div>Loading...</div>,
  });
  const NotFound = Loadable({
    loader: () => import('../components/notfound'),
    loading: ()=><div>Loading...</div>,
  });

//当路径为home时加载home组件
export const routes =[
    {
        path:'/home',
        component:Home
    },
    {
        path:'/404',
        component:NotFound
    }
]

重塑组件

作用是为了样式可以继承

安装:npm install styled-components

//使用:(首字母一定要大写)
import styled from 'styled-components'
//创建一个div ,组件名称为Div 
var Div = styled.div`
    background:red;
    font-weight:bold;
    p{
        font-style:italic;
    }
`
function App() {
  return (
    <>
        <Div>
            <p>111</p>
            <p>222</p>
        </Div>
    </>
  );
}

空标记Fragment

//方案1
import React,{Fragment} from 'react';
function App() {
  return (
    <Fragment>
        <One />
    </Fragment>
  );
}
//方案2
import React from 'react';
function App() {
  return (
    <>
        <One />
    </>
  );
}

react 其他stuff

类似vue的slot插槽

react没有这个东西,而是children

但是跟插槽一样,在使用组件的地方内部直接放入要放的标签,然后用this.props.children[0]使用

{
  //children是个数组
  this.props.children[0]
}

类似vue的v-html

<div dangerouslySetInnerHTML={{
                    __html:this.state.content
  }}></div>

Hooks

React目前提供的Hook
hook    用途
useState    设置和改变state,代替原来的state和setState
useEffect   代替原来的生命周期,componentDidMount,componentDidUpdate 和 componentWillUnmount 的合并版
useLayoutEffect 与 useEffect 作用相同,但它会同步调用 effect
useMemo 控制组件更新条件,可根据状态变化控制方法执行,优化传值
useCallback useMemo优化传值,usecallback优化传的方法,是否更新
useRef  跟以前的ref,一样,只是更简洁了
useContext  上下文爷孙及更深组件传值
useReducer  代替原来redux里的reducer,配合useContext一起使用
useDebugValue   在 React 开发者工具中显示自定义 hook 的标签,调试使用。
useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。
  • userState():状态钩子

纯函数组件没有状态,useState()用于为函数组件引入状态

/*
  count - 定义的值
  setCount - 更改值的方法
  0 - 设置的初始值
*/
const [ count, setCount ] = useState(0)
  • useContext():共享状态钩子

该钩子的作用是,在组件之间共享状态,作用就是做到状态分发

但是必须要有Provider进行组件包裹

简单来说useContext只是拿值的,值是在provider上面定义的

import React,{ useContext } from 'react'
const Ceshi = () => {
  const AppContext = React.createContext({})
  const A =() => {
    const { name } = useContext(AppContext)
    return (
        <p>我是A组件的名字{name}<span>我是A的子组件{name}</span></p>
    )
}
const B =() => {
  const { name } = useContext(AppContext)
  return (
      <p>我是B组件的名字{name}</p>
  )
}
  return (
    <AppContext.Provider value={{name: 'hook测试'}}>
    <A/>
    <B/>
    </AppContext.Provider>
  )
}
export default Ceshi 

  • useReducer():Action钩子

    useReducer 通常被用于替代useState来统一管理状态,避免过多的state带来的开发不便

我们知道,在使用React的过程中,如遇到状态管理,我们一般会用到Redux,而React本身是不提供状态管理的。而useReducer()为我们提供了状态管理。首先,关于redux我们都知道,其原理是我们通过用户在页面中发起action,从而通过reducer方法来改变state,从而实现页面和状态的通信。而Reducer的形式是(state, action) => newstate。类似,我们的useReducer()是这样的

const [state, dispatch] = useReducer(reducer, initialState)

它接受reducer函数和状态的初始值作为参数,返回一个数组,其中第一项为当前的状态值,第二项为发送action的dispatch函数。下面我们依然用来实现一个计数器。
和redux一样,我们是需要通过页面组件发起action来调用reducer方法,从而改变状态,达到改变页面UI的这样一个过程。所以我们会先写一个Reducer函数,然后通过useReducer()返回给我们的state和dispatch来驱动这个数据流。思路就是这样,下面我们上代码

import React,{useReducer} from 'react'

const AddCount = () => {
const reducer = (state, action) =>  {
 if(action.type === ''add){
  return {
  ...state,
  count: state.count +1,
  }
 }else {
   return state
  }
 }
const addcount = () => { 
  dispatch({
    type: 'add'
  })
 }
const [state, dispatch] = useReducer(reducer, {count: 0})
return (
<>
<p>{state.count}</p>
<button onClick={addcount}>count++</button>
</>
)
}
export default AddCount

  • useEffect():副作用钩子

useEffect()接受两个参数,第一个参数是你要进行的异步操作,第二个参数是一个数组,用来给出Effect的依赖项。只要这个数组发生变化,useEffect()就会执行。当第二项省略不填时,useEffect()会在每次组件渲染时执行。这一点类似于类组件的componentDidMount。下面我们通过代码模拟一个异步加载数据。

const [myValue,settMyValue] = useState('') 
//useEffect(处理函数,【依赖】)   副作用
useEffect(()=>{
   //创建或者更新的时候执行
   console.log('useEffect')
 },[myValue])//依赖myValue 每当这个参数有变化的时候就会触发当前这个useEffect
  • useMemo

把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。

function Example() {
    const [count, setCount] = useState(1);
    const [val, setValue] = useState('');
 
    const getNum = useMemo(() => {
        return Array.from({length: count * 100}, (v, i) => i).reduce((a, b) => a+b)
    }, [count])
 
    return <div>
        <h4>总和:{getNum}</h4>
        <div>
            <button onClick={() => setCount(count + 1)}>+1</button>
            <input value={val} onChange={event => setValue(event.target.value)}/>
        </div>
    </div>;
}
  • useCallback

把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子组件时,它将非常有用。

  • useCallback和 useMemo区别

useMemouseCallback接收的参数都是一样,都是在其依赖项发生变化后才执行,都是返回缓存的值,区别在于useMemo返回的是函数运行的结果,useCallback返回的是函数。

  • useMemo 缓存的结果是回调函数中return回来的值,主要用于缓存计算结果的值,应用场景如需要计算的状态
  • useCallback 缓存的结果是函数,主要用于缓存函数,应用场景如需要缓存的函数,因为函数式组件每次任何一个state发生变化,会触发整个组件更新,一些函数是没有必要更新的,此时就应该缓存起来,提高性能,减少对资源的浪费;另外还需要注意的是,useCallback应该和React.memo配套使用,缺了一个都可能导致性能不升反而下降。
useCallback(fn, deps) 相当于 useMemo(() => fn, deps)

react事件绑定this的原因

如何绑定this

//第一种,在constructor里面用bind绑定this
constructor(props) {
    super(props);
    this.state = {isToggleOn: true};

    // This binding is necessary to make `this` work in the callback
  //这种方法是把原型方法handleClick()改变为实例方法handleClick(),并且强制制定这个方法中的this指向当前的实例
    this.handleClick = this.handleClick.bind(this);
  }
//第二种,声明方法的时候使用箭头函数
  handleClick = () => {
    this.setState(prevState => ({
      isToggleOn: !prevState.isToggleOn
    }));
  }
//第三种,调用的时候使用箭头函数
   render() {
    return (
      <button onClick={ () => { this.handleClick } }> 
        {this.state.isToggleOn ? 'ON' : 'OFF'} 
      </button>
    );
  }

在 render() 函数中,react对{}的解析会把this的指向解除了,所以在这里导致的 this 丢失,指向了 undefined。 生成实例的过程中,构造器函数 constructor() 必执行。当执行到下面语句时,分析代码: this.handleClick = this.handleClick.bind(this); 赋值语句右侧,this.handleClick 执行,首先在当前实例上查找 handleClick 方法,当前实例没有,然后沿原型链向上查找到原型方法 handleClick ,再执行 bind(this) ,将原型方法中的 this 指向新生成的实例

react事件处理机制

  • JS原生处理机制

    在JavaScript中,事件的触发实质上是要经过三个阶段:事件捕获、目标对象本身的事件处理和事件冒泡,假设在div中触发了click事件,实际上首先经历捕获阶段会由父级元素将事件一直传递到事件发生的元素,执行完目标事件本身的处理事件后,然后经历冒泡阶段,将事件从子元素向父元素冒泡。

React事件并没有原生的绑定在真实的DOM上,而是使用了行为委托方式实现事件机制,所以react的事件是合成事件((Synethic event),不是原生事件

合成事件与原生事件的区别

  • 写法不同,合成事件是驼峰写法,而原生事件是全部小写
  • 执行时机不同,合适事件全部委托到document上,而原生事件绑定到DOM元素本身
  • 合成事件中可以是任何类型,比如this.handleClick这个函数,而原生事件中只能是字符串

合成事件:React不会将事件处理函数直接绑定到真实的节点上,而是把所有的事件绑定到结构的最外层,使用一个统一的事件监听器。这个监听器维持了一个映射,保存所有组件内部的事件监听和处理函数。当事件发生时,首先被这个统一的事件监听器处理,然后在映射里找到真正的事件处理函数并调用。

  • React中的事件机制分为两个阶段:事件注册和事件触发:
  1. 事件注册  
    React在组件加载(mount)和更新(update)时,其中的ReactDOMComponent会对传入的事件属性进行处理,对相关事件进行注册和存储。document中注册的事件不处理具体的事件,仅对事件进行分发。ReactBrowserEventEmitter作为事件注册入口,担负着事件注册和事件触发。注册事件的回调函数由EventPluginHub来统一管理,根据事件的类型(type)和组件标识(_rootNodeID)为key唯一标识事件并进行存储。
  2. 事件执行
    事件执行时,document上绑定事件ReactEventListener.dispatchEvent会对事件进行分发,根据之前存储的类型(type)和组件标识(_rootNodeID)找到触发事件的组件。ReactEventEmitter利用EventPluginHub中注入(inject)的plugins(例如:SimpleEventPluginEnterLeaveEventPlugin)会将原生的DOM事件转化成合成的事件,然后批量执行存储的回调函,回调函数的执行分为两步,第一步是将所有的合成事件放到事件队列里面,第二步是逐个执行。需要注意的是,浏览器原生会为每个事件的每个listener创建一个事件对象,可以从这个事件对象获取到事件的引用。这会造成高额的内存分配,React在启动时就会为每种对象(比如点击事件,滚动事件)分配内存池,用到某一个事件对象时就可以从这个内存池进行复用,节省内存。

React事件处理的特性

React的事件系统和浏览器事件系统相比,主要增加了两个特性:事件代理和事件自动绑定

React 监听的是 document 上的冒泡阶段。事件冒泡到 document 后,React 将事件再派发到组件树中,然后事件开始在组件树 DOM 中走捕获冒泡流程。

1、事件代理

  1. 区别于浏览器事件处理方式,React并未将事件处理函数与对应的DOM节点直接关联,而是在顶层使用了一个全局事件监听器监听所有的事件;
  2. React会在内部维护一个映射表记录事件与组件事件处理函数的对应关系;
  3. 当某个事件触发时,React根据这个内部映射表将事件分派给指定的事件处理函数;
  4. 当映射表中没有事件处理函数时,React不做任何操作;
  5. 当一个组件安装或者卸载时,相应的事件处理函数会自动被添加到事件监听器的内部映射表中或从表中删除。

2、事件自动绑定

  1. 在JavaScript中创建回调函数时,一般要将方法绑定到特定的实例,以保证this的正确性;

  2. 在React中,每个事件处理回调函数都会自动绑定到组件实例(使用ES6语法创建的例外);

注意:事件的回调函数被绑定在React组件上,而不是原始的元素上,即事件回调函数中的this所指的是组件实例而不是DOM元素;

3、合成事件

1.与浏览器事件处理稍微有不同的是,React中的事件处理程序所接收的事件参数是被称为“合成事件(SyntheticEvent)的实例。合成事件是对浏览器原生事件跨浏览器的封装,并与浏览器原生事件有着同样的接口,如stopPropagation(),preventDefault()等,并且这些接口是跨浏览器兼容的。
2.如果需要使用浏览器原生事件,可以通过合成事件的nativeEvent属性获取

React在事件处理的优点

  1. 几乎所有的事件代理(delegate)到 document ,达到性能优化的目的
  2. 对于每种类型的事件,拥有统一的分发函数 dispatchEvent
  3. 事件对象(event)是合成对象(SyntheticEvent),不是原生的,其具有跨浏览器兼容的特性
  4. react内部事件系统实现可以分为两个阶段: 事件注册、事件分发,几乎所有的事件均委托到document上,而document上事件的回调函数只有一个:ReactEventListener.dispatchEvent,然后进行相关的分发
  5. 对于冒泡事件,是在 document 对象的冒泡阶段触发。对于非冒泡事件,例如focus,blur,是在 document 对象的捕获阶段触发,最后在 dispatchEvent 中决定真正回调函数的执行

diff算法

fibber是一个双缓存结构

dom-diff就是对比老的fiber链表和新的jsx数组对比,生成新的fiber链表过程

拿老的fiber节点与心的jsx节点进行对比,每个fiber节点都有type 和 key 只要不一样就删旧增新

如果是单个节点就直接根据type key进行替换删除

多个节点就有两轮,第一轮进行节点的更新 第二轮才是替换删除

在第一轮里,如果有type或key不一样,则马上终止第一轮循环,并且创建一个map对象,把所有对象以key,dom键值对形式存入遍历,然后找是否有相同的节点,有的话就移动这个找到的相同节点

连续setState

连续的情况只会执行一次,会把连续做的state做个合并,如果key是一样的就会后面覆盖前面的,不是一样的就做合并,原理是基于object.assign合并对象

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,951评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,606评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,601评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,478评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,565评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,587评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,590评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,337评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,785评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,096评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,273评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,935评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,578评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,199评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,440评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,163评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,133评论 2 352

推荐阅读更多精彩内容