React基础知识学习笔记

1. 项目初始化

使用指令创建一个react项目

$ npx create-react-app react-demo
  • npx create-react-app 是固定命令,create-react-app是React脚手架的名称
  • react-demo是项目名称
  • npx 命令会帮助我们临时安装create-react-app包,然后初始化项目完成之后会自自动删掉,所以不需要全局安装create-react-app
## 使用 npx
npx create-react-app my-app --template typescript

## 使用 npm
npm init react-app my-app --template typescript

## 使用 yarn
yarn create react-app my-app --template typescript

直接使用 typescript

启动项目使用

$ yarn start
or
$ npm start

2. JSX中使用js表达式

const name = '小武'
const getAge = () => {
  return 12
}
const flag = true
function App() {
  return (
    <div className="App">
      App
      <br></br>
      { name }
      <br/>
      {getAge()}
      <br/>
      {flag ? '你好' : '你坏'}
    </div>
  );
}

export default App;
    1. 字符串、数值、布尔值、null、undefined、object( [] / {} )
    1. 1 + 2、'abc'.split('')、['a', 'b'].join('-')
    1. fn()
      注意
      if 语句/ switch-case 语句/ 变量声明语句,这些叫做语句,不是表达式,不能出现在 {} 中!!

3. 列表渲染

// 注意:遍历列表时同样需要一个类型为number/string不可重复的key,提高diff性能
// key仅仅内部使用,不会出现在真实dom元素上
const sports = [
  {id: 1, name: '足球'},
  {id: 2, name: '篮球'},
  {id: 3, name: '排球'},
]

function App() {
  return (
    <div className="App">
      <ul>
        {
          sports.map(item => <li key={item.id}>{item.name}</li>)
        }
      </ul>
    </div>
  );
}

export default App;

4. 条件渲染

// 条件渲染 三元表达式 逻辑&& 
// 复杂的多分支条件渲染 通过一个函数来写分支逻辑,模板中只负责调用

const flag = true
const showHtag = (type) => {
  switch (type) {
    case 1:
      return <h1>这是 h1</h1>
    case 2:
      return <h2>这是 h2</h2>
    case 3:
      return <h3>这是 h3</h3>
  
    default:

      return <p>默认是 p标签</p>
  }
}
function App() {
  return (
    <div className="App">
      { flag ? (
        <div>
          <span>这里是三元运算显示的内容</span>
        </div>) : null
      }
      {
        true && (<span>这里是逻辑&&实现的显示</span>)
      }
      { showHtag() }
      { showHtag(1) }
      { showHtag(2) }
      { showHtag(3) }
    </div>
  );
}

export default App;

5. JSX样式处理

// JSX样式处理
// 1. 行内样式 - style 直接写在模板里面是双括号,内层括号表示对象
// 2. 类名 - className(推荐)方便动态控制

import "./app.css"

const styleObj = {
  color: 'blue',
  fontSize: 20
}

const activeClass = true
function App() {
  return (
    <div className="App">
      <p>行内样式</p>
      <div style={{color: 'red', fontSize: 18}}>测试行内样式1</div>
      <div style={styleObj}>测试行内样式2</div>
      <p>类名样式</p>
      <div className={ activeClass ? "active" : ''}>测试类名样式</div>
    </div>
  );
}

export default App;

JSX注意事项

    1. JSX必须有一个根节点,如果没有根节点,可以使用<></>(幽灵节点)替代
    1. 所有标签必须形成闭合,成对闭合或者自闭合都可以
    1. JSX中的语法更加贴近JS语法,属性名采用驼峰命名法 class -> className for -> htmlFor
    1. JSX支持多行(换行),如果需要换行,需使用() 包裹,防止bug出现

6. 组件事件

import React from 'react';
// 定义函数组件
// 在传递自定义参数时
// 只需要一个额外参数时 {clickHandler} -> {() => clickHandler('自定义参数')}
// 急需要e也需要额外参数 {(e) => clickHandler(e, '自定义参数')}
function HelloFn() {
  const sayHello = (e, message) => {
    e.preventDefault();
    console.log('你好', e, message);
  };
  return (
    <div>
      这是函数组件!
      <a onClick={(e) => sayHello(e, '额外参数')} href='https://www.baidu.com/'>
        百度
      </a>
    </div>
  );
}
// 定义类组件
class HelloC extends React.Component {
  sayYes = () => {
    // 这里的this指向的是正确的当前的组件实例对象
    // 可以非常方便的通过this关键词拿到组件实例身上的其他属性或者方法
    console.log(this);
    console.log('Yes');
  };
  clickHandler1() {
    // 这里的this 不指向当前的组件实例对象而指向undefined 存在this丢失问题
    console.log(this);
  }
  render() {
    return <div onClick={this.sayYes}>创建一个类组件</div>;
  }
}

// 定义类组件
function App() {
  return (
    <div className='App'>
      {/* 渲染函数组件 */}
      <HelloFn />
      <HelloFn></HelloFn>
      {/* 渲染类组件 */}
      <HelloC />
      <HelloC></HelloC>
    </div>
  );
}
export default App;

注意:
函数组件

  1. 组件的名称必须首字母大写,react内部会根据这个来判断是组件还是普通的HTML标签
  2. 函数组件必须有返回值,表示该组件的 UI 结构;如果不需要渲染任何内容,则返回 null
  3. 组件就像 HTML 标签一样可以被渲染到页面中。组件表示的是一段结构内容,对于函数组件来说,渲染的内容是函数的返回值就是对应的内容
  4. 使用函数名称作为组件标签名称,可以成对出现也可以自闭合
    类组件
  5. 类名称也必须以大写字母开头
  6. 类组件应该继承 React.Component 父类,从而使用父类中提供的方法或属性
  7. 类组件必须提供 render 方法render 方法必须有返回值,表示该组件的 UI 结构

7. 组件状态

// 组件状态 类组件演示
import React from 'react';
class TestComponent extends React.Component {
  // 初始化状态
  // 可以定义各种属性,全都是当前组件的状态
  state = {
    name: '小武',
  };

  changeName = () => {
    // 修改state中的name
    // 修改状态, 不要直接修改state中的值,必须通过setState方法进行修改
    this.setState({
      name: '小亮',
    });
  };
  render() {
    return (
      <div>
        {/* 读取状态 */}
        这是 TestComponent 当前name为:{this.state.name}
        <button onClick={this.changeName}>修改name</button>
      </div>
    );
  }
}

class Counter extends React.Component {
  state = {
    count: 0,
    list1: [1, 2, 3, 4, 5],
    list2: [1, 2, 3, 4, 5],
    person: {
      name: '小熊女',
      age: 12,
    },
  };
  changeCount = () => {
    this.setState({
      count: this.state.count + 1, // 操作简单数据类型
      list1: [...this.state.list1, 6, 7], // 增加列表
      list2: this.state.list2.filter((item) => item % 2), // 删除列表中某个元素
      person: {
        ...this.state.person,
        // 覆盖原来的属性 就可以达到修改对象中属性的目的
        name: '二丫头',
      },
    });
  };
  render() {
    return (
      <>
        <ul>
          {this.state.list1.map((item, index) => (
            <li key={index}>{item}</li>
          ))}
        </ul>
        <ul>
          {this.state.list2.map((item, index) => (
            <li key={index}>{item}</li>
          ))}
        </ul>
        <div>{this.state.person.name}</div>
        <button onClick={this.changeCount}>点击了{this.state.count}次</button>
      </>
    );
  }
}

/**
 * 总结:
 * 虽然现在大多数使用的是函数类组件,但老项目的维护可能需要类组件
 * 1. 编写组件其实就是编写原生js类或者函数
 * 2. 定义状态必须通过state 实例属性的方法 提供一个对象 名称是固定的就叫做state
 * 3. 修改state中的任何属性 都不可以直接赋值 必须通过setState方法 这个方法来自继承得到
 * 4. 这里的this关键词 很容易出现指向错误的问题 上面的写法是最推荐和最规范的 没有this指向问题
 *
 */

const App = () => {
  return (
    <div className='App'>
      <TestComponent />
      <Counter />
    </div>
  );
};

export default App;

08. 受控组件

import React from 'react';
class Counter extends React.Component {
  // 1. 声明用来控制input value的react组件状态
  state = {
    message: '信息',
  };
  inputChange = (e) => {
    console.log('input触发change事件', e);
    // 4. 拿到输入框最新值交给state中的message
    this.setState({
      message: e.target.value,
    });
  };
  render() {
    return (
      // 2. 给input框的value属性绑定 react state
      // 3. 给input框绑定一个change事件,为了拿到输入框中的数据
      <input
        type='text'
        value={this.state.message}
        onChange={this.inputChange}
      />
    );
  }
}
const App = () => {
  return (
    <div>
      <Counter />
    </div>
  );
};

export default App;

09.非受控组件

import React, { createRef } from 'react';
class Counter extends React.Component {
  // 非受控组件通过类似 vue 的 ref 属性获取元素
  // 类组件通过 createRef 操作内部元素
  myInpu = createRef();
  getMsg = () => {
    console.log(this.myInpu.current.value);
  };
  render() {
    return (
      <>
        <input type='text' ref={this.myInpu} />
        <button onClick={this.getMsg}>获取input信息</button>
      </>
    );
  }
}
const App = () => {
  return (
    <div>
      <Counter />
    </div>
  );
};

export default App;

10. 组件练习

import './index.css';
import avatar from './images/avatar.png';
import React from 'react';
import { v4 as uuid } from 'uuid';

class App extends React.Component {
  // 依赖的数据
  state = {
    // hot: 热度排序  time: 时间排序
    tabs: [
      {
        id: 1,
        name: '热度',
        type: 'hot',
      },
      {
        id: 2,
        name: '时间',
        type: 'time',
      },
    ],
    active: 'hot',
    list: [
      {
        id: 1,
        author: '刘德华',
        comment: '给我一杯忘情水',
        time: new Date('2021-10-10 09:09:00'),
        // 1: 点赞 0:无态度 -1:踩
        attitude: 1,
      },
      {
        id: 2,
        author: '周杰伦',
        comment: '哎哟,不错哦',
        time: new Date('2021-10-11 09:09:00'),
        // 1: 点赞 0:无态度 -1:踩
        attitude: 0,
      },
      {
        id: 3,
        author: '五月天',
        comment: '不打扰,是我的温柔',
        time: new Date('2021-10-11 10:09:00'),
        // 1: 点赞 0:无态度 -1:踩
        attitude: -1,
      },
    ],
    comment: '',
  };
  formateTime = (time) => {
    return `${time.getFullYear()}-${time.getMonth() + 1}-${time.getDate()}`;
  };
  switchTab = (type) => {
    this.setState({
      active: type,
    });
  };
  commentInput = (e) => {
    // 获取评论
    this.setState({
      comment: e.target.value,
    });
  };
  submitComment = () => {
    // 发表评论
    this.setState({
      list: [
        ...this.state.list,
        {
          id: uuid(),
          author: '小亮',
          comment: this.state.comment,
          time: new Date(),
          // 1: 点赞 0:无态度 -1:踩
          attitude: 0,
        },
      ],
    });
  };
  // 删除评论
  delComment = (id) => {
    console.log(id);
    this.setState({
      list: this.state.list.filter((item) => item.id !== id),
    });
  };
  // 点赞与否
  switchLike = (user, type) => {
    const { id, attitude } = user;
    this.setState({
      list: this.state.list.map((item) => {
        if (item.id === id) {
          let isLike = 0;
          if (type === 'like') {
            isLike = attitude === 1 ? 0 : 1;
          } else {
            isLike = attitude === -1 ? 0 : -1;
          }
          return {
            ...item,
            attitude: isLike,
          };
        } else {
          return item;
        }
      }),
    });
  };
  render() {
    return (
      <div className='App'>
        <div className='comment-container'>
          {/* 评论数 */}
          <div className='comment-head'>
            <span>5 评论</span>
          </div>
          {/* 排序 */}
          <div className='tabs-order'>
            <ul className='sort-container'>
              {this.state.tabs.map((tab) => (
                <li
                  key={tab.id}
                  onClick={() => this.switchTab(tab.type)}
                  className={tab.type === this.state.active ? 'on' : ''}
                >
                  按{tab.name}排序
                </li>
              ))}
            </ul>
          </div>

          {/* 添加评论 */}
          <div className='comment-send'>
            <div className='user-face'>
              <img className='user-head' src={avatar} alt='' />
            </div>
            <div className='textarea-container'>
              <textarea
                cols='80'
                rows='5'
                placeholder='发条友善的评论'
                className='ipt-txt'
                value={this.state.comment}
                onChange={this.commentInput}
              />
              <button className='comment-submit' onClick={this.submitComment}>
                发表评论
              </button>
            </div>
            <div className='comment-emoji'>
              <i className='face'></i>
              <span className='text'>表情</span>
            </div>
          </div>

          {this.state.list.map((user) => (
            <div className='comment-list' key={user.id}>
              <div className='list-item'>
                <div className='user-face'>
                  <img className='user-head' src={avatar} alt='' />
                </div>
                <div className='comment'>
                  <div className='user'>{user.author}</div>
                  <p className='text'>{user.comment}</p>
                  <div className='info'>
                    <span className='time'>{this.formateTime(user.time)}</span>
                    <span
                      onClick={() => this.switchLike(user, 'like')}
                      className={user.attitude === 1 ? 'like liked' : 'like'}
                    >
                      <i className='icon' />
                    </span>
                    <span
                      onClick={() => this.switchLike(user, 'hate')}
                      className={user.attitude === -1 ? 'hate hated' : 'hate'}
                    >
                      <i className='icon' />
                    </span>
                    <span
                      className='reply btn-hover'
                      onClick={() => this.delComment(user.id)}
                    >
                      删除
                    </span>
                  </div>
                </div>
              </div>
            </div>
          ))}
        </div>
      </div>
    );
  }
}

export default App;

11. 组件通信父传子

import React from 'react';

// 1.  props是只读对象(readonly)
// 根据单项数据流的要求,子组件只能读取props中的数据,不能进行修改

// 2. props可以传递任意数据
// 数字、字符串、布尔值、数组、对象、函数、JSX

// function SonFc(props) {
//   return (
//     <div>
//       这是函数式子组件--{props.mesg}--{props.age}--
//       {props.isMan ? '传递了true的布尔值' : ''}--{props.list}--
//       <button onClick={props.cb}>获取父组件传递的函数</button>
//       --这是传递的jsx的内容,浏览器展示的是真实的dom,类似vue插槽:{props.child}
//     </div>
//   );
// }
// 解构获取
function SonFc({ mesg, age, isMan, list, cb, child }) {
  return (
    <div>
      这是函数式子组件--{mesg}--{age}--
      {isMan ? '传递了true的布尔值' : ''}--{list}--
      <button onClick={cb}>获取父组件传递的函数</button>
      --这是传递的jsx的内容,浏览器展示的是真实的dom,类似vue插槽:{child}
    </div>
  );
}

class SonCl extends React.Component {
  render() {
    return <div>这是类子组件--{this.props.mesg}</div>;
  }
}

class App extends React.Component {
  state = {
    mesg: '父组件传递给子组件的值',
  };
  getMes = () => {
    console.log('传递函数');
  };
  render() {
    return (
      <div className='App'>
        <SonFc
          mesg={this.state.mesg}
          age={20}
          isMan={true}
          list={[1, 2, 3, 4]}
          cb={this.getMes}
          child={<span>this is child</span>}
        />
        <SonCl mesg={this.state.mesg} />
      </div>
    );
  }
}

export default App;

12. 子传父

父组件给子组件传递回调函数,子组件调用

import React from 'react';

function SonFc(props) {
  const { getSonMes } = props;
  function clickHandler() {
    getSonMes('传递数据给父组件');
  }
  return (
    <div>
      子组件
      <button onClick={clickHandler}>传递数据给父组件</button>
      <button onClick={() => getSonMes('传递数据给父组件')}>
        传递数据给父组件
      </button>
    </div>
  );
}

class App extends React.Component {
  state = {
    mesg: '父组件传递给子组件的值',
  };
  getSonMes = (sonMsg) => {
    console.log(sonMsg);
  };
  render() {
    return (
      <div className='App'>
        <SonFc getSonMes={this.getSonMes} />
      </div>
    );
  }
}

export default App;

13. 兄弟通过父组件传值

import React from 'react';

// 组件B通过父组件给A组件传数据

function SonA(props) {
  return (
    <div style={{ border: '1px solid red' }}>
      <h3>子组件A</h3>
      {props.mesg}
    </div>
  );
}
function SonB(props) {
  const state = {
    message: '这是B组件来的',
  };
  return (
    <div style={{ border: '1px solid blue' }}>
      <h3>子组件B</h3>
      <button onClick={() => props.getSonMes(state.message)}>
        给A组件传值
      </button>
    </div>
  );
}

class App extends React.Component {
  state = {
    mesg: '',
  };
  getSonMes = (sonMsg) => {
    this.setState({
      mesg: sonMsg,
    });
  };
  render() {
    return (
      <div className='App'>
        <SonA mesg={this.state.mesg} />
        <SonB getSonMes={this.getSonMes} />
      </div>
    );
  }
}

export default App;

14. 跨组件通信

import React, { createContext } from 'react';
const { Provider, Consumer } = createContext();

// 跨组件通信
// 上层组件和下层组件关系是相对的,只要存在即可,通常会将根组件作为数据提供方
// 语法是固定的,提供的位置 value提供数据 获取的位置 {value => 使用value接收 }
function ComA() {
  return (
    <div style={{ border: '1px solid red' }}>
      <h3>组件A</h3>
      <Consumer>{(value) => <span>{value}</span>}</Consumer>
      <ComB />
    </div>
  );
}
function ComB() {
  return (
    <div style={{ border: '1px solid blue' }}>
      <h3>组件B</h3>
      <Consumer>{(value) => <span>{value}</span>}</Consumer>
    </div>
  );
}

class App extends React.Component {
  state = {
    mesg: '根组件传的值',
  };
  render() {
    return (
      <Provider value={this.state.mesg}>
        <div className='App'>
          这是根组件App
          <ComA />
        </div>
      </Provider>
    );
  }
}

export default App;

15. 阶段小练习

import React from 'react';

// 跨组件通信

function ListItem({ name, price, info, id, removeItem }) {
  return (
    <div style={{ border: '1px solid red' }}>
      <h3>{name}</h3>
      <p>{price}</p>
      <p>{info}</p>
      <button onClick={() => removeItem(id)}>删除</button>
    </div>
  );
}

class App extends React.Component {
  state = {
    list: [
      {
        id: 1,
        name: '超级好吃的棒棒糖',
        price: 18.8,
        info: '开业大酬宾,全场8折',
      },
      {
        id: 2,
        name: '超级好吃的大鸡腿',
        price: 34.2,
        info: '开业大酬宾,全场8折',
      },
      {
        id: 3,
        name: '超级无敌的冰激凌',
        price: 14.2,
        info: '开业大酬宾,全场8折',
      },
    ],
  };
  removeItem = (id) => {
    this.setState({
      list: this.state.list.filter((item) => item.id !== id),
    });
  };
  render() {
    return (
      <div className='App'>
        {this.state.list.map((item) => (
          <ListItem
            key={item.id}
            {...item}
            removeItem={this.removeItem}
          ></ListItem>
        ))}
      </div>
    );
  }
}

export default App;

16. children属性

import React from 'react';

// 跨组件通信

function ListItem({ children }) {
  // children(); // children 传入的是函数时就不能放到标签中了
  return <div style={{ border: '1px solid red' }}>ListItem {children}</div>;
}

class App extends React.Component {
  render() {
    return (
      <div className='App'>
        <ListItem>
          {/* children 可以是普通文本、普通标签元素、函数 / 对象、JSX,多个child在组件的children中会是数组形式,可以进行数组的操作*/}
          {/* this is string */}
          <div>this is child</div>
          <p>this is p</p>
          {/* {() => console.log(123)} */}
          {/* {
            <div>
              <p>{'this is p'}</p>
            </div>
          } */}
        </ListItem>
      </div>
    );
  }
}

export default App;

17. props校验

import React from 'react';
import PropTypes from 'prop-types';

// props校验-场景和使用
// 1. 安装属性校验包:yarn add prop-types
// 2. 导入prop-types 包
// 3. 使用 组件名.propTypes = {} 给组件添加校验规则

// // 常见类型
// optionalFunc: PropTypes.func,
// // 必填 只需要在类型后面串联一个isRequired
// requiredFunc: PropTypes.func.isRequired,
// // 特定结构的对象
// optionalObjectWithShape: PropTypes.shape({
//  color: PropTypes.string,
//  fontSize: PropTypes.number
// })

const List = (props) => {
  const arr = props.list;
  return (
    <ul>
      {arr.map((item) => (
        <li key={item} style={props.style}>
          {item}
        </li>
      ))}
    </ul>
  );
};

// props校验-默认值
// 函数组件
function List1({ pageSize = 10 }) {
  return <div>此处展示props的默认值:{pageSize}</div>;
}
// 类组件
class List2 extends React.Component {
  static defaultProps = {
    pageSize: 10,
  };
  render() {
    return <div>此处展示props的默认值:{this.props.pageSize}</div>;
  }
}
List.propTypes = {
  list: PropTypes.array.isRequired,
  style: PropTypes.shape({
    color: PropTypes.string,
    fontSize: PropTypes.number,
  }),
};

class App extends React.Component {
  state = {
    list: [1, 2, 3, 4],
  };
  render() {
    return (
      <div className='App'>
        <List
          list={this.state.list}
          style={{ color: 'red', fontSize: 24 }}
        ></List>
        <List1></List1>
        <List2></List2>
      </div>
    );
  }
}

export default App;

18. 类组件的生命周期

import React from 'react';

// 有生命周期(类组件 实例化  函数组件没有生命周期 不需要实例化)
// 挂载时会先后执行 constructor、render、componentDidMount
// 更新时会先后执行 render、componentDidUpdate
// 卸载时执行 componentWillUnmount

// 生命周期 - 挂载阶段
// constructor  创建组件时,最先执行,初始化的时候只执行一次  1. 初始化state  2. 创建 Ref 3. 使用 bind 解决 this 指向问题等
// render   每次组件渲染都会触发  渲染UI(注意: 不能在里面调用setState() )
// componentDidMount    组件挂载(完成DOM渲染)后执行,初始化的时候执行一次 1. 发送网络请求   2.DOM操作

class ComA extends React.Component {
  // 如果数据在组件中和视图有联系,定义到state中
  // 而如果数据状态不与视图绑定 定义成一个普通的实例属性即可
  // state中尽量保持精简
  timer = '';
  componentDidMount() {
    this.timer = setInterval(() => {
      console.log('定时1秒触发');
    }, 1000);
  }
  componentWillUnmount() {
    console.log('componentWillUnmount');
    // 清除定时器
    clearInterval(this.timer);
  }

  render() {
    return <div>挂载实例</div>;
  }
}

class App extends React.Component {
  constructor() {
    super();
    console.log('constructor');
  }
  componentDidMount() {
    console.log('componentDidMount');
    // 类似于 vue 的mounted
  }
  componentDidUpdate() {
    // 不要直接调用setState
    console.log('componentDidUpdate');
  }
  state = {
    count: 0,
    show: true,
  };
  addCount = () => {
    this.setState({
      count: this.state.count + 1,
      show: !this.state.show,
    });
  };
  render() {
    console.log('render');
    return (
      <div className='App'>
        this is App
        <button onClick={this.addCount}>{this.state.count}</button>
        {this.state.show ? <ComA /> : null}
      </div>
    );
  }
}

export default App;

19. 删、查练习

import React from 'react';
import './App.css';
import { Input, Popconfirm, Table } from 'antd';
import axios from 'axios';
const { Search } = Input;
// 需要启动指令 yarn mock-serve 启动本地后台

class App extends React.Component {
  state = {
    list: [],
    columns: [
      {
        title: '任务编号',
        dataIndex: 'id',
        key: 'id',
      },
      {
        title: '任务名称',
        dataIndex: 'name',
        key: 'name',
      },
      {
        title: '任务描述',
        dataIndex: 'des',
        key: 'des',
      },
      {
        title: '操作',
        dataIndex: 'do',
        key: 'do',
        render: (_, record) =>
          this.state.list.length > 1 ? (
            <Popconfirm
              title='确定删除吗?'
              onConfirm={() => this.delOneData(_, record)}
            >
              <a>删除</a>
            </Popconfirm>
          ) : null,
      },
    ],
  };
  // 搜索
  onSearch = async (value) => {
    const res = await axios.get(`http://localhost:3001/data/?q=${value}`);
    this.setState({
      list: res.data,
    });
  };

  // 删除
  delOneData = async (_, record) => {
    await axios.delete(`http://localhost:3001/data/${record.id}`);
    this.loadData();
  };
  // 加载列表
  loadData = async () => {
    const res = await axios.get('http://localhost:3001/data');
    res.data.forEach((item, index) => {
      item.key = index + 1;
    });
    this.setState({
      list: res.data,
    });
  };
  componentDidMount() {
    this.loadData();
  }
  render() {
    return (
      <div className='App'>
        <Search
          placeholder='请输入搜索内容'
          allowClear
          enterButton='搜索'
          size='large'
          onSearch={this.onSearch}
        />
        <Table columns={this.state.columns} dataSource={this.state.list} />
      </div>
    );
  }
}

export default App;

20 hook-useState

import React, { useState } from 'react';

// useState为函数组件提供状态(state)

// 状态的读取与修改
// const [count, setCount] = useState(0)
// 1. useState 传过来的参数作为 count 的初始值
// 2. [count, setCount] 这里的写法是一个解构赋值 useState 返回值是一个数组
//    名字可以自己定义
//    顺序不可交换 第一个参数是数据状态 第二个参数是修改数据的方法
// 3. setCount函数 作用用来修改 count 依旧保持不能直接修改原值,还是生成一个新值替换原值
// 4. count 和 setCount 是一对 是绑定在一起的 setCount 只能用来修改对应的 count 值

// 当调用setCount时 更新过程

// 首次渲染
// 首次被渲染时,组件内部的代码会被执行一次
// 其中的 useState 也会跟着执行 注意:初始值只在首次渲染时生效

// 更新渲染 setCount 都会更新
// 1. app 组件会再次渲染 这个函数会被再次执行
// 2. useState 再次执行 得到新的count值 不是0 而是修改之后的1 模板会用新值渲染

// useState 初始值只在首次渲染生效 后续只要调用 setCount 整个 app 组件中的代码都会执行, 此时的 count 每次拿到的都是最新值

// useState 的调函数中的逻辑只会在组件初始化的时候执行一次
// 参数只会在组件的初始渲染中起作用,后续渲染时会被忽略。如果初始 state 需要通过计算才能获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用

function getDefaultV(v) {
  for (let i = 1; i < 10000; i++) {}
  return v;
}

function Counter(props) {
  const [count, setCount] = useState(() => {
    // 在首次渲染时需要大量计算,在后面这个计算就不需要了
    // return props.count;
    return getDefaultV(props.count);
  });
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>{count}</button>
    </div>
  );
}

const App = () => {
  // 参数:状态初始值比如,传入 0 表示该状态的初始值为 0
  // 返回值:数组,包含两个值:1 状态值(state) 2 修改该状态的函数(setState)
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(true);
  const [list, setList] = useState([]);
  console.log('执行了');
  // 1. useState 函数可以执行多次,每次执行互相独立,每调用一次为函数组件提供一个状态
  // 2. useState 注意事项
  //    a. 只能出现在函数组件或者其他hook函数中
  //    b. 不能嵌套在if/for/其它函数中(react按照hooks的调用顺序识别每一个hook)
  //    c. 可以通过开发者工具查看hooks状态
  function test() {
    setCount(count + 1);
    setFlag(!flag);
    setList([...list, 1]);
  }
  return (
    <div>
      <div>count: {count}</div>
      <div>flag: {flag ? 'true' : 'false'}</div>
      <div>list: {list.join('-')}</div>
      <button onClick={() => test()}>修改</button>
      <Counter count={10}></Counter>
      <Counter count={20}></Counter>
    </div>
  );
};

export default App;

21. hook-useEffect

import { useState, useEffect } from 'react';
import axios from 'axios';
// 对于 React 组件来说,主作用就是根据数据(state/props)渲染 UI,除此之外都是副作用(比如,手动修改 DOM)
// useEffect函数的作用就是为react函数组件提供副作用处理的!

// 通过修改状态更新组件时,副作用也会不断执行

const App = () => {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('小亮');

  // 1. 此时无依赖项 组件首次渲染执行一次,以及不管是哪个状态更改引起组件更新时都会重新执行
  // useEffect(() => {
  //   // 副作用 dom操作
  //   document.title = `当前已点击了${count}次`;
  // });

  // 2. 添加空数组 组件只在首次渲染时执行一次
  // useEffect(() => {
  //   // 副作用 dom操作
  //   document.title = `当前已点击了${count}次`;
  // }, []);

  // 3. 添加特定依赖项 副作用函数在首次渲染时执行,在依赖项发生变化时重新执行 useEffect 回调函数中用到的数据(比如,count,name)就是依赖数据,就应该出现在依赖项数组中,如果不添加依赖项就会有bug出现
  // useEffect(() => {
  //   document.title = `当前已点击了${count}次`;
  // }, [count]);

  // 4. 清理副作用
  // 如果想要清理副作用 可以在副作用函数中的末尾return一个新的函数,在新的函数中编写清理副作用的逻辑
  // 注意执行时机为:
  //   1. 组件卸载时自动执行
  //   2. 组件更新时,下一个useEffect副作用函数执行之前自动执行
  // useEffect(() => {
  //   const timerId = setInterval(() => {
  //     setCount(count + 1);
  //   }, 1000);
  //   return () => {
  //     // 用来清理副作用的事情
  //     clearInterval(timerId);
  //   };
  // }, [count]);

  // useEffect - 发送网络请求
  useEffect(() => {
    async function fetchData() {
      const res = await axios.get('http://geek.itheima.net/v1_0/channels');
      console.log(res.data.data.channels);
    }
    fetchData();
  }, []);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>修改count{count}</button>
      <button onClick={() => setName(name + '1')}>修改name</button>
    </div>
  );
};

export default App;

22. hook测试

app.js

import { useWindowScroll } from './useWindowScroll';
import { useLocalStorage } from './useLocalStorage';
const App = () => {
  const [y] = useWindowScroll();
  const [message, setMessage] = useLocalStorage('hook', '666');
  const storageData = () => {
    setMessage('777');
  };
  return (
    <div style={{ height: '12000px' }}>
      {y} / {message}
      <button onClick={storageData}>存入数据</button>
    </div>
  );
};

export default App;

useLocalStorage.js

import { useState, useEffect } from 'react';
// 自动同步到本地LocalStorage
export function useLocalStorage(key, defaultValue) {
  const [message, setMessage] = useState(defaultValue);
  useEffect(() => {
    window.localStorage.setItem(key, message);
  }, [key, message]);
  return [message, setMessage];
}

useWindowScroll.js

import { useState, useEffect } from 'react';
// 获取滚动距离Y
export function useWindowScroll() {
  const [y, setY] = useState(0);
  useEffect(() => {
    const scrollHandler = () => {
      const h = document.documentElement.scrollTop;
      setY(h);
    };
    window.addEventListener('scroll', scrollHandler);
    return () => window.removeEventListener('scroll', scrollHandler);
  });

  return [y];
}

23. useRef

import React, { useRef, useEffect } from 'react';

// useRef 获取真实dom或类组件实例
// 1. 导入 useRef 函数
// 2. 执行 useRef 函数并传入null,返回值为一个对象 内部有一个current属性存放拿到的dom对象(组件实例)
// 3. 通过ref 绑定 要获取的元素或者组件

// 函数组件由于没有实例,不能使用ref获取,如果想获取组件实例,必须是类组件

class Foo extends React.Component {
  say = () => {
    console.log('hello');
  };
  render() {
    return <div>Foo</div>;
  }
}

const App = () => {
  const h1Ref = useRef(null);
  const fooRef = useRef(null);
  useEffect(() => {
    console.log(h1Ref.current);
    console.log(fooRef.current);
  }, []);
  return (
    <div className='App'>
      <h1 ref={h1Ref}>标签h1</h1>
      <Foo ref={fooRef}></Foo>
    </div>
  );
};

export default App;

24. hook-useContext

单独使用 useContext 和 createContext

import React, { useContext, createContext, useState } from 'react';

// 创建 Context 对象 跨组件通信
const Context = createContext();

function ComA() {
  return (
    <div>
      ComA
      <br />
      <ComB></ComB>
    </div>
  );
}

function ComB() {
  // 底层组件通过useContext函数获取数据
  const count = useContext(Context);
  return (
    <div>
      ComB
      <br />
      {count}
    </div>
  );
}

const App = () => {
  const [count, setCount] = useState(0);
  return (
    // 顶层组件通过Provider 提供数据
    <Context.Provider value={count}>
      <div className='App'>
        <ComA></ComA>
        <button onClick={() => setCount(count + 1)}>+</button>
      </div>
    </Context.Provider>
  );
};

export default App;

抽离
App.js

import React, { useContext } from 'react';
import Context from './contex';
// 创建 Context 对象
// const Context = createContext();

function ComA() {
  return (
    <div>
      ComA
      <br />
      <ComB></ComB>
    </div>
  );
}

function ComB() {
  // 底层组件通过useContext函数获取数据
  const count = useContext(Context);
  return (
    <div>
      ComB
      <br />
      {count}
    </div>
  );
}

const App = () => {
  return (
    // 顶层组件通过Provider 提供数据

    <div className='App'>
      <ComA></ComA>
    </div>
  );
};

export default App;

index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import Context from './contex';

// Context 如果要传递的数据 只需要在整个应用初始化的时候传递一次就可以选在在index.js文件中实现数据提供

// 如果 Context 需要传递数据并且将来还需要在对数据做修改 底层组件也需要跟着一起改变 就写道app.js
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <Context.Provider value={100}>
    <App />
  </Context.Provider>,
);

contex.js

import { createContext } from 'react';
// 创建 Context 对象 跨组件通信
const Context = createContext();

export default Context;

25. react-router

App.js

import React from 'react';
import {
  HashRouter,
  BrowserRouter,
  Routes,
  Route,
  Link,
} from 'react-router-dom';
import Home from './Home';
import About from './About';
// 添加路由首先需要安装 react-router 包
// yarn add react-router-dom

// BrowserRouter history 模式 h5的 history.pushState API实现
// HashRouter 监听url hash值实现
// * Link 指定跳转的组件 to用来配置路由地址
// * Routes 路由出口 路由对应的组件会在这里进行渲染
// * Route 指定路径和组件的对应关系 path代表路径 element代表组件 成对出现

const routes = [
  {
    path: '/',
    name: '首页',
    component: Home(),
  },
  {
    path: '/about',
    name: '关于',
    component: About(),
  },
];
const App = () => {
  return (
    // 这种路由是一个非hash模式的
    <div className='App'>
      {/* <BrowserRouter>
        {routes.map((item, index) => (
          <Link to={item.path} key={index}>
            {item.name}
          </Link>
        ))}
        <Routes>
          {routes.map((item, index) => (
            <Route
              path={item.path}
              key={index}
              element={item.component}
            ></Route>
          ))}
        </Routes>
      </BrowserRouter> */}

      {/* <BrowserRouter>
        <Link to='/'>首页</Link>
        <Link to='/about'>关于</Link>
        <Routes>
          <Route path='/' element={<Home />}></Route>
          <Route path='/about' element={<About />}></Route>
        </Routes>
      </BrowserRouter> */}

      <HashRouter>
        <Link to='/'>首页</Link>
        <Link to='/about'>关于</Link>
        <Routes>
          <Route path='/' element={<Home />}></Route>
          <Route path='/about' element={<About />}></Route>
        </Routes>
      </HashRouter>
    </div>
  );
};

export default App;

About.js

function About() {
  return <div>this is About</div>;
}
export default About;

Home.js

function Home() {
  return <div>this is Home</div>;
}
export default Home;

26. 编程式导航

App.js

import React from 'react';
import {
  HashRouter,
  BrowserRouter,
  Routes,
  Route,
  Link,
} from 'react-router-dom';
import Home from './Home';
import About from './About';
import Login from './Login';
const App = () => {
  return (
    <div className='App'>
      <BrowserRouter>
        <Link to='/'>首页</Link>
        <Link to='/about'>关于</Link>
        <Routes>
          <Route path='/' element={<Home />}></Route>
          <Route path='/about' element={<About />}></Route>
          <Route path='/login' element={<Login />}></Route>
        </Routes>
      </BrowserRouter>
    </div>
  );
};

export default App;

About.js

function About() {
  return <div>this is About</div>;
}
export default About;

Home.js

function Home() {
  return <div>this is Home</div>;
}
export default Home;

Login.js

import { useNavigate } from 'react-router-dom';
// navigate 要跳转到的路由
// 注: 如果在跳转时不想添加历史记录,可以添加额外参数replace 为true.
function Login() {
  const navigate = useNavigate();
  return (
    <div>
      Login
      <button onClick={() => navigate('/about', { replace: true })}>
        跳转到关于
      </button>
    </div>
  );
}

export default Login;

27. 路由传参

App.js

import React from 'react';
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
import Home from './Home';
import About from './About';
import Login from './Login';
const App = () => {
  return (
    <div className='App'>
      <BrowserRouter>
        <Link to='/'>首页</Link>
        <Link to='/about'>关于</Link>
        <Routes>
          <Route path='/' element={<Home />}></Route>
          <Route path='/about/:id' element={<About />}></Route>
          <Route path='/login' element={<Login />}></Route>
        </Routes>
      </BrowserRouter>
    </div>
  );
};

export default App;

Login.js

import { useNavigate } from 'react-router-dom';
// navigate 要跳转到的路由
// 注: 如果在跳转时不想添加历史记录,可以添加额外参数replace 为true.

// 1. searchParams 传参 通过路由的查询参数传递,接收在跳转的路由中 通过 useSearchParams 获取
// 2. params 传参 需要在路由处进行设置占位符
// 第一种和第二种在同一个路由下参数是不兼容的

function Login() {
  const navigate = useNavigate();
  return (
    <div>
      第一种参数
      <button onClick={() => navigate('/about?id=1001&id=1002&name=PP')}>
        跳转到关于
      </button>
      <br />
      第二种传参
      <button onClick={() => navigate('/about/10001')}>跳转到关于</button>
    </div>
  );
}

export default Login;

About.js

import { useSearchParams, useParams } from 'react-router-dom';
// 通过 useSearchParams 获取查询参数传递的数据
function About() {
  const [params] = useSearchParams();
  const res = params.getAll('id'); // 通过getAll可以获取所有参数值
  const name = params.get('name'); // 通过get获取第一个需要的参数值
  console.log(res);

  let secParams = useParams();
  return (
    <div>
      this is About获取到的第一种路由参数是{res}-{name}
      <br />
      this is About获取到的第二种路由参数是{secParams.id}
    </div>
  );
}
export default About;

Home.js

function Home() {
  return <div>this is Home</div>;
}
export default Home;

28. 嵌套路由

App.js

import React from 'react';
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
import Login from './Login';
import Layout from './Layout';
import Board from './Board';
import Article from './Article';
import NotFound from './NotFound';
const App = () => {
  return (
    <div className='App'>
      <BrowserRouter>
        <Link to='/'>首页</Link>
        <Link to='/login'>关于</Link>
        <Routes>
          <Route path='/' element={<Layout />}>
            {/* 二级路由嵌套在一级路由下 */}
            {/* 首次渲染完毕就需要显示的二级路由,把原本的路径path属性用index替换 */}
            <Route index element={<Board />} />
            {/* <Route path='board' element={<Board />}></Route> */}
            <Route path='article' element={<Article />}></Route>
          </Route>
          <Route path='/login' element={<Login />}></Route>
          {/* 当url的路径在整个路由配置中都找不到对应的path */}
          <Route path='*' element={<NotFound />}></Route>
        </Routes>
      </BrowserRouter>
    </div>
  );
};

export default App;

Layout.js

import { Outlet, Link } from 'react-router-dom';
// 设置board为默认路由,那么,跳转的path也需要修改为默认路由
function Layout() {
  return (
    <div>
      {/* 二级路由的path等于 一级path + 二级path  */}
      Layout - <Link to='/'>board</Link> -{' '}
      {/* <Link to='/board'>board</Link> -  */}
      <Link to='/article'>artical</Link>
      {/* 二级路由出口 */}
      <Outlet />
    </div>
  );
}

export default Layout;

Article.js

function Article() {
  return <div>Article</div>;
}

export default Article;

Board.js

function Board() {
  return <div>Board</div>;
}

export default Board;

Login.js

function Login() {
  return <div>Login</div>;
}

export default Login;

NotFound.js

function NotFound() {
  return <div>对不起,页面丢失了</div>;
}

export default NotFound;

29. 集中式路由配置

App.js

import React from 'react';
import { BrowserRouter, Routes, Route, useRoutes } from 'react-router-dom';
import Login from './Login';
import Layout from './Layout';
import Board from './Board';
import Article from './Article';
import NotFound from './NotFound';

// 1. 准备一个路由数组 数组中定义所有的路由对应关系
const routesList = [
  {
    path: '/',
    element: <Layout />,
    children: [
      {
        element: <Board />,
        index: true,
      },
      {
        path: 'article',
        element: <Article />,
      },
    ],
  },
  {
    path: '/login',
    element: <Login />,
  },
  {
    path: '*',
    element: <NotFound />,
  },
];
// 2. 使用useRoutes方法传入routesList生成Routes组件
function WrapperRoutes() {
  let element = useRoutes(routesList);
  return element;
}
const App = () => {
  return (
    <div className='App'>
      <BrowserRouter>
        {/* 3. 替换之前的Routes组件 */}
        <WrapperRoutes />
      </BrowserRouter>
    </div>
  );
};

export default App;

30. mobx

安装 mobx 和中间件工具 mobx-react-lite 只能函数组件中使用

yarn add  mobx  mobx-react-lite

是在 src 目录下新建一个 store 文件夹,在其中存放 mobx 逻辑

30-1 基本使用

App.js

import React from 'react';
// 使用store
// 导入counterStore
import { counter } from './store/counter';
// 导入observer方法
import { observer } from 'mobx-react-lite';

const App = () => {
  return (
    <div className='App'>
      <div>{counter.count}</div>
      <button onClick={counter.addCound}>+</button>
      <div>{counter.getO.join('-')}</div>
      <button onClick={counter.changeList}>修改数组</button>
    </div>
  );
};
// 包裹组件让视图响应数据变化
export default observer(App);

store\counter.js

import { makeAutoObservable, computed } from 'mobx';

// 通过get关键词 定义计算属性

class CounterStore {
  count = 0; // 定义数据
  list = [0, 1, 2, 3, 4, 5];
  constructor() {
    makeAutoObservable(this); // 响应式处理
  }
  // 定义修改数据的方法
  addCound = () => {
    this.count++;
    console.log(this.count);
  };
  // 定义计算属性
  get getO() {
    return this.list.filter((item) => item > 2);
  }
  // 修改数组
  changeList = () => {
    let lth = this.list.length;
    return this.list.push(lth);
  };
}
const counter = new CounterStore();
export { counter };

30-2. 模块化

App.js

import { useEffect } from 'react';
import { useStore } from './store';
import { observer } from 'mobx-react-lite';
function App() {
  const { channlStore, counterStore, listStore } = useStore();
  // 1. 使用数据渲染组件
  // 2. 触发action函数发送异步请求
  useEffect(() => {
    channlStore.setChannelList();
  }, []);
  return (
    <>
      <ul>
        {channlStore.channelList.map((item) => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
      <br />
      counterStore {counterStore.count}
      <button onClick={counterStore.addCound}>+</button>
      <br />
      listStore {listStore.list.join('-')}
      <button onClick={listStore.changeList}>修改ListStore</button>
    </>
  );
}
// 让组件可以响应数据的变化[也就是数据一变组件重新渲染]
export default observer(App);

store文件夹中:
index.js

import React from 'react';
import { channelStore } from './channelStore';
import { counterStore } from './counterStore';
import { listStore } from './listStore';

class RootStore {
  constructor() {
    this.channlStore = channelStore;
    this.counterStore = counterStore;
    this.listStore = listStore;
  }
}

const rootStore = new RootStore();

// context机制的数据查找链  Provider如果找不到 就找createContext方法执行时传入的参数
const context = React.createContext(rootStore);

const useStore = () => React.useContext(context);
export { useStore };

channelStore.js

// 异步的获取

import { makeAutoObservable, observable, action } from 'mobx';
import axios from 'axios';
// 此处界面有警告
class ChannelStore {
  channelList = [];
  constructor() {
    makeAutoObservable(this);
  }
  // 只要调用这个方法 就可以从后端拿到数据并且存入channelList
  setChannelList = async () => {
    const res = await axios.get('http://geek.itheima.net/v1_0/channels');
    this.channelList = res.data.data.channels;
  };
}
const channelStore = new ChannelStore();
export { channelStore };

counterStore.js

import { makeAutoObservable } from 'mobx';

// 通过get关键词 定义计算属性

class CounterStore {
  count = 0; // 定义数据
  list = [0, 1, 2, 3, 4, 5];
  constructor() {
    makeAutoObservable(this); // 响应式处理
  }
  // 定义修改数据的方法
  addCound = () => {
    this.count++;
    console.log(this.count);
  };
  // 定义计算属性
  get getO() {
    return this.list.filter((item) => item > 2);
  }
  // 修改数组
  changeList = () => {
    let lth = this.list.length;
    return this.list.push(lth);
  };
}
const counterStore = new CounterStore();
export { counterStore };

listStore.js

import { makeAutoObservable } from 'mobx';

class ListStore {
  list = ['react', 'vue'];
  constructor() {
    makeAutoObservable(this); // 响应式处理
  }
  // 修改数组
  changeList = () => {
    let lth = this.list.length - 1;
    return this.list.push('框架' + lth);
  };
}
const listStore = new ListStore();
export { listStore };

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

推荐阅读更多精彩内容