Javascript

万字总结记录我的React学习路程(附各个代码参考)

本文主要是介绍万字总结记录我的React学习路程(附各个代码参考),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

说在最前面

本文是自己最近再学习React的总结和记录。尽力把每个示例代码都写了出来,尽力写的通俗易懂一些,也算是锻炼自己的讲述能力,据说给别人讲的话学的会更快,所以写下此文。

如有错误或者其他,还望各位批评指正

还没整理完全,趁着没开学,我会抓紧时间学习和更新

JSX

简介

在JS里面写的一些标签称为JSX语法

基本语法

在返回的时候不需要加引号

import React from 'react';
import ReactDOM from 'react-dom';
import logo from './logo.svg';

// 基本
const jsx = <h1>hello react</h1>

// 变量
const name = '123456'
const jsxName = <h1>{ name }</h1>

      // 函数
const user = { firstName: 'tom', lastName: 'jerry' };

function formatName(user) {
  return user.firstName + ' ' + user.lastName;
}

const jsxFunction = <h2>{ formatName(user) }</h2>

      // 对象
const greet = <p>hello, Jerry</p>
const jsxObject = <h2>{ greet }</h2>

      
     // 三元表达式
const showTitle = true;
const title = showTitle ? <h2>{ name }</h2> : null;
const jsxOthers = (<div>{
  /* 条件语句句 */
} { title }
</div>);

// 循环
const arr = [1, 2, 3].map(num => <li key={ num }>{ num } </li>)
const jsxArray = (<div>    {/* 数组 */ }
  <ul>{ arr }</ul>
</div>)


// 注意
const jsxAtt = (<div>
  {
    /* 属性:静态值⽤用双引号,动态值⽤用花括号;class、for等 要特殊处理理。
    * 像class需要写成className,for需要写成htmlFor,并且属性名需要采用驼峰命名法
    * */
  }
<img src={ logo } style={ { width: 100 } } className="img"/></div>)

// 函数传参
function greeting(name) {
  if (name) {
    return <h1>Hello, {name}!</h1>;
  }
  return <h1>Hello, Stranger.</h1>;
}
let name2 = '测试';
const element = greeting(name2);

// 循环
let names = ['张三','李四','王五'];
let elements = [];
for(let i=0;i<names.length;i++){
  elements.push(<li>{names[i]}</li>);
}
export { jsx, jsxName, jsxFunction, jsxObject, jsxOthers, jsxArray,jsxAtt,element,elements }
复制代码

组件

​ 可以将UI切分成一些独立的、可复用的部件,

class 组件

​ class组件通常拥有状态生命周期继承于Component,实现 render方法

基本

import React, { Component } from 'react';
class Home extends Component {
  render() {
    return <h1>Hello, Class Components</h1>;
  }
}
复制代码

类组件中的状态管理(秒表实例)

import React, { Component } from 'react';
class State extends Component {
    
  // 使用state属性维护状态,在构造函数中初始化状态
  constructor(props) {
    super(props);
    this.state = { date: new Date(), number: 0 };
    // this.changeNumber = this.changeNumber.bind(this)
  }

  // 组件挂载时启动定时器每秒更新状态
  componentDidMount() {
    this.timerID = setInterval(() => {
      // 使⽤用setState方法更新状态
      this.setState({ date: new Date() });
    }, 1000);
  }

  // 组件卸载时停止定时器
  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  // 这里不绑定this 会报错 两种方法 第一种 用箭头函数。第二种在constructor进行绑定声明
    // setState是异步执行的。执行这个结果是 log 是2不是3 证明是异步执行
  changeNumber = () => {
    this.setState({ number: this.state.number + 1 } )
    this.setState({ number: this.state.number + 2 } )
    // 这里不能进行直接修改
    console.log(this.state.number)
  }
  // 同步改变
  changeNumber2 = ()=> {
    this.setState((nextState)=>{ return { number: nextState.number + 1 } } )
    this.setState((nextState)=>{ return { number: nextState.number + 2 } } )
    console.log(this.state.number)

    // 使用定时器
    // setTimeout(() => {  this.setState((nextState)=>{ return { number: nextState.number + 1 } } );  console.log(this.state.counter); }, 0);

    // 原生事件中修改状态
    // componentDidMount(){    document.body.addEventListener('click', this.setState((nextState)=>{ return { number: nextState.number + 1 } } ), false) }
  }
  render() {
    return (
        <div>
          { this.state.date.toLocaleTimeString() }
          <p>{ this.state.number }</p>
          <p>setState是异步执行的</p>
          <button onClick={this.changeNumber}>异步改变number</button>
          <button onClick={this.changeNumber2}>同步改变number</button>
        </div>
    )
  }
}
复制代码

状态的设置和改变

constructor设置状态。使用setState来改变状态。示例如上

关于setState

注意,setState是 异步的!!!!

如何证明?

见示例代码中changeNumber的描述和实现

如何实现同步?

常见的有三种方法:

  1. 利用函数传递参数
  2. 使用定时器
  3. 直接找到DOM元素直接修改

上述三种方法在示例代码中都可以找到。

函数的this 绑定

一般有两种方法

  1. constructor里面用bind方法绑定。例如this.changeNumber = this.changeNumber.bind(this)
  2. 利用ES6的箭头函数。示例changeNumber = () => { // 执行代码 }。(**原因解释:**箭头函数默认指向定义它时,所处上下文的对象的this指向)

function组件

基本

import React from "react"; 
import User from "./pages/User";
function App() {  
    return (    
        <div>      
        	<User />    
        </div>  
    ); 
}
export default App;
复制代码

后文的 react HOOKs会做详细解释

组件通讯

props通讯

适用于父子组件通讯

// index.js 
ReactDOM.render(<App title="测试代码" />, document.querySelector('#root'));
// App.js 
<h2>{this.props.title}</h2>  // 输出为 测试代码
复制代码

Props 的只读性

官网原话 :组件无论是使用函数声明还是通过 class 声明,都决不能修改自身的 props

状态提升

官方解释: 通常,多个组件需要反映相同的变化数据,这时我们建议将共享状态提升到最近的共同父组件中去。

实例代码

import React from 'react'
class Child_1 extends React.Component{
  constructor(props){
    super(props)
  }
  render(){
    return (
        <div>
          <h1>{this.props.value+'child 1'}</h1>
        </div>
    )
  }
}
class Child_2 extends React.Component{
  constructor(props){
    super(props)
  }
  render(){
    return (
        <div>
          <h1>{this.props.value+'child 2'}</h1>
        </div>
    )
  }
}
class Parent extends React.Component {
  constructor(props){
    super(props)
    this.state = {
      txt:"我是父组件"
    }
    this.handleChange = this.handleChange.bind(this)
  }
  handleChange(e){
    this.setState({
      txt:e.target.value
    })
  }
  render(){
    return (
        <div>
          <input type="text" value={this.state.txt} onChange={this.handleChange}/>
          <p>{this.state.txt}</p>
          <Child_1 value={this.state.txt}/>
          <Child_2 value={this.state.txt}/>
        </div>
    )
  }
}
export default Parent

复制代码

context 跨组件通讯

跨层级组件之间通信

单层传递

// ContextFather.js
import React from 'react';
import ContextChild from './child';
const Context = React.createContext()
const Provider = Context.Provider
const Consumer = Context.Consumer
const data = {
  name: '我是father数据',
}

function ContextApp() {
  return (
      <div>
        <Provider value={ data }>
          <Consumer>
            {
              value => <ContextChild {...value}/>
            }
          </Consumer>
        </Provider>
      </div>
  )
}
export default ContextApp

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

class ContextChild extends Component {
  constructor(props) {
    super(props);
  }
  render() {
    console.log(this.props)
    return (<h1>hello {this.props.name}</h1>);
  }
}
export default ContextChild
复制代码

这样可以获取到传递过来的name

多层传递

// Context.js
import React from 'react';

const Context = React.createContext()
const Provider = Context.Provider
const Consumer = Context.Consumer

export {Provider,Consumer}
// ContextFather
import React from 'react';
import ContextChild from './child';
import {Consumer,Provider} from './Context';

const data = {
  name: '我是father数据',
}

function ContextApp() {
  return (
      <div>
        <Provider value={ data }>
      // 这个层级不需要就不用写 Consumer
          <ContextChild/>
        </Provider>
      </div>
  )
}
export default ContextApp

// child.js
import React, { Component } from 'react';
import ContextChild2 from './child2';
import {Consumer} from './Context';

class ContextChild extends Component {
  constructor(props) {
    super(props);
  }
  render() {
    return (
        <div>
          <h1>我是第一层</h1>
        // 这里获取的是一个空值,没有数据
          <h2>{this.props.name}</h2>
        // 这个层级需要就吧数据传递过去
          <Consumer>
            {
              value => <ContextChild2 {...value}/>
            }
          </Consumer>

        </div>
    )
  }
}
export default ContextChild

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

class ContextChild2 extends Component {
  constructor(props) {
    super(props);
  }
  render() {
    console.log(this.props)
    console.log('我是第二层的')
    return (
        <div>
          <h1>我是第二层的</h1>
          <h1>{this.props.name}</h1>
        </div>
    );
  }
}
export default ContextChild2
复制代码

特别注意

  1. 不能有多个提供者,例如在每个文件里 写上Context.js里面的语句。则会获取不到数据。不同的provider提供的值不一致
  2. 在React的官方文档中,Context被归类为高级部分(Advanced),属于React的高级API,但官方并不建议在稳定版的App中使用Context。不过,这并非意味着我们不需要关注Context。事实上,很多优秀 的React组件都通过Context来完成⾃自己的功能,比如react-redux

redux 类似Vuex

没学过Vue的可以理解为全局状态管理,即你在哪里都能访问的到(我个人是这么理解的)

知识扩展

Reducer

解释: reducer 就是一个纯函数,接收旧的 stateaction,返回新的 state

reduce

本例子来源于MDN

const array1 = [1, 2, 3, 4];
const reducer = (total, currentValue) => total + currentValue;

// 1 + 2 + 3 + 4
console.log(array1.reduce(reducer));
// expected output: 10

// 5 + 1 + 2 + 3 + 4
console.log(array1.reduce(reducer, 5));
// expected output: 15
复制代码
compose(没太理解)

compose就是执行一系列的任务(函数)

思考

如何输出1 2 3

function f1() {  console.log("1"); } 
function f2() {  console.log("2"); }
function f3() {  console.log("3"); }
复制代码

第一种

f1();f2();f3();

第二种

f3(f2(f1()))

第三种

function compose(...funcs) {
  // funcs 是一个函数列表
  if (funcs.length === 0) {
    console.log('empty');
  } else if (funcs.length === 1) {
    return funcs[0];
  } else {
    return funcs.reduce(
        (left, right) => (...args) => {
          // console.log(args)
          // console.log(right)
          // console.log(left)
          right(left(...args)) // 相当于 f3(f2(f1()))
        }
    );
  }
}
compose(f1,f2,f3)()
复制代码

Redux

使用步骤
  1. 需要一个store来存储数据
  2. storereducer初始化state并定义state修改规则
  3. 通过dispatch一个action来提交对数据的修改
  4. action提交到reducer函数里,根据传入的actiontype,返回新的 state

实例代码

// 创建store  store/ReduxStore
import { createStore } from 'redux';
// 创建数据并指定行为
const counterReducer = (state = 0, action) => {
  switch (action.type) {
    case 'add':
      return state + 1
    case 'minus':
      return state - 1
    default:
      return state
  }
}
// 创建一个store
const store = createStore(counterReducer)
export default store
复制代码
// ReduxPage
import React, { Component } from 'react';

import store from '../store/ReduxStore';

export default class ReduxPage extends Component {
  componentDidMount() { // 挂在之后
    store.subscribe(() => { // 执行订阅
      console.log('subscribe');
      this.forceUpdate(); // 重新render
      //this.setState({}); // 这个也可以,内部方法也是forceUpdate
    });
  }
// 执行行为
  add = () => {
      // 派发action
    store.dispatch({ type: 'add' });
  };
  minus = () => {
    store.dispatch({ type: 'minus' });
  };
  stayStatic = () => {
    store.dispatch({ type: 'others' });
  };

  render() {
    console.log('store', store);
    return (
        <div>
          <h1>ReduxPage</h1>
          <p>获取store数据</p>
          <p>{ store.getState() }</p>
          <button onClick={ this.add }>+</button>
          <button onClick={ this.minus }>-</button>
          <button onClick={ this.stayStatic }>+-</button>
        </div>
    );
  }
}
复制代码
注意

如果点击后 数据没有更新。则是订阅状态没有改变(也就是没有render

着重注意点
  1. createStore 创建store
  2. reducer初始化、修改状态函数
  3. getState 获取状态值
  4. dispatch提交更新
  5. subscribe 变更订阅

react-redux

React的方式写来Redux

提供了两个api

  1. Provider 为后代组件提供store(类似Context)
  2. connect 为组件提供数据和变更⽅方法
react-thunk

原来的只可以同步执行,安装之后,可以进行异步操作

react-logger

只可以再开发环境下使用。用来输出各个action

store

大家会发现其实还是有 redux的引入,说明react-redux也是基于Redux

import { createStore, applyMiddleware,combineReducers } from 'redux';
import logger from 'import React, { Component } from 'react';
import { connect } from 'react-redux';

class ReactReduxPage extends Component {
  render() {
    const { num, add, minus,asyAdd } = this.props;
    // console.log(this.props) // 原本这里面什么都没有,再进行connect映射后出现了
    return (
        <div>
          <h1>ReactReduxPage</h1>
          <p>{ num }</p>
          <button onClick={ add }>+</button>
          <button onClick={ minus }>-</button>
          <button onClick={ asyAdd }>asyAdd</button>
        </div>
    );
  }
}

const mapStateToProps = state => {
  console.log(state)
  return { num: state };
  // return { num: state.counter };// 加了combineReducers 之后需要这么做 因为是一个对象
};

const mapDispatchToProps = {
  add: () => {
    return { type: 'add' };
  },
  minus: () => {
    return { type: 'minus' };
  },
  asyAdd: ()=> dispatch =>{
    setTimeout(()=>{
      dispatch ({type: 'add'})
    },1000)
  }
};
export default connect(
    mapStateToProps,//状态映射 mapStateToProps
    mapDispatchToProps,//派发事件映射
)(ReactReduxPage)
';
import thunk from 'redux-thunk';


const counterReducer = (state = 0, action) => {
  switch (action.type) {
    case 'add':
      return state + 1
    case 'minus':
      return state - 1
    default:
      return state
  }
}
// const store = createStore(counterReducer)
// const store = createStore(combineReducers({counter: counterReducer}), applyMiddleware(logger, thunk))
const store = createStore(counterReducer, applyMiddleware(logger, thunk))
export default store
复制代码

applyMiddleware提供扩展中间件(有顺序,这个事先执行logger,在执行thunk

combineReducers 命名空间,当你有多个store的时候,使用这个,就可以用对象的方式访问各个store

ReactReduxPage
import React, { Component } from 'react';
import { connect } from 'react-redux';

class ReactReduxPage extends Component {
  render() {
    const { num, add, minus,asyAdd } = this.props;
    // console.log(this.props) // 原本这里面什么都没有,再进行connect映射后出现了
    return (
        <div>
          <h1>ReactReduxPage</h1>
          <p>{ num }</p>
          <button onClick={ add }>+</button>
          <button onClick={ minus }>-</button>
          <button onClick={ asyAdd }>asyAdd</button>
        </div>
    );
  }
}

const mapStateToProps = state => {
  console.log(state)
  return { num: state };
  // return { num: state.counter };// 加了combineReducers 之后需要这么做 因为是一个对象
};

const mapDispatchToProps = {
  add: () => {
    return { type: 'add' };
  },
  minus: () => {
    return { type: 'minus' };
  },
  asyAdd: ()=> dispatch =>{
    setTimeout(()=>{
      dispatch ({type: 'add'})
    },1000)
  }
};
export default connect(
    mapStateToProps,//状态映射 mapStateToProps
    mapDispatchToProps,//派发事件映射
)(ReactReduxPage)
复制代码

当然,如果嫌弃太乱,可以单独抽离出来放。最后导入进来就可以

组件复合

这个也是官方推荐的做法: 我们推荐使用组合而非继承来实现组件间的代码重用。

学过Vue的同学,我个人理解为类似于Vue的插槽

// Layout.js
import React, { Component } from 'react';
import TabBar from '../components/TabBar';
class Layout extends Component{
  constructor(props) {
    super(props);
  }
  componentDidMount() {
    const {title = '首页'} = this.props
    document.title = title
  }
    
  // 兼容具名和不具名  compatible
  compatibleNamed(children) {
    let doc = []
    if (children.$$typeof) {
      doc.push(children)
    } else {
      for (let item in children){
        doc.push(children[item])
      }
    }
    return doc
  }
  render() {
    console.log(this.props)
    return (
        <div>
          <h1>我是布局页面</h1>
          {/* 不具名的使用*/}
          {/*{*/}
          {/*  this.props.children*/}
          {/*}*/}
          {/* 这个是具名的使用*/}
          {
            this.props.children.btn
          }
          <h2>兼容过后</h2>
       {this.compatibleNamed(this.props.children).map((item,index)=>{
            return (
                <div key={index}>
                  {item}
                </div>
            )
          })}
          { this.props.showTab && <TabBar/> }
        </div>
    )
  }
}
export default Layout
// Home.js
import React, { Component } from 'react';
import Layout from '../ComponentCombination/Layout';

class Home extends Component {
  render() {
    return (
        <DisNamed/>
    )
  }
}

function Named(props) {
  return (
      <Layout showTab={ false } title='商城首页'>
        <h1>我是首页</h1>
      </Layout>
  )
}
function DisNamed() {
  return (
      <Layout showTab={ false } title='商城首页'>
        { {
          btn: <button>我是按钮</button>
        } }
      </Layout>
  )
}
export default Home
// TabBar.js
import React, { Component } from 'react';
class TabBar extends Component{
  render() {
    return (
        <div>
          <h1>我是TabBar</h1>
        </div>
    )
  }
}
export default TabBar
复制代码

不具名

上面例子容易混淆,写一个简单点的

import React, { Component } from 'react';
import TabBar from '../components/TabBar';
class Layout extends Component{
  constructor(props) {
    super(props);
  }
  render() {
    console.log(this.props)
    return (
        <div>
          <h1>我是布局页面</h1>
          {/* 不具名的使用*/}
          {
            this.props.children
          }
          { this.props.showTab && <TabBar/> }
        </div>
    )
  }
}
export default Layout
复制代码

this.props.children所展示的是两个Layout之间的内容

例如

<Layout>
    <h1>hello</h1>
    </Layout>
复制代码

this.props.children展示的就是<h1>hello</h1>

具名

//Layout
import React, { Component } from 'react';
import TabBar from '../components/TabBar';
class Layout extends Component{
  constructor(props) {
    super(props);
  }
  render() {
    console.log(this.props)
    return (
        <div>
          <h1>我是布局页面</h1>
          {/* 这个是具名的使用*/}
          {
            this.props.children.btn
          }
          { this.props.showTab && <TabBar/> }
        </div>
    )
  }
}
export default Layout


// Home.js
import React, { Component } from 'react';
import Layout from '../ComponentCombination/Layout';

class Home extends Component {
  render() {
    return (
        <Named/>
    )
  }
}
function DisNamed() {
  return (
      <Layout showTab={ false } title='首页'>
        { {
          btn: <button>我是按钮</button>
        } }
      </Layout>
  )
}
export default Home
复制代码

在传递具名组件的时候,需要用双{{}}包裹。使用的就当属性使用就可以了

差异与兼容

具名不具名 的差异在于传递过来的时候this.props.children是否含有$$typeof属性。如果不含有,则是具名。反之,含有则是不具名

如何兼容?

知道了两者差异。就从差异下手

  compatibleNamed(children) {
    let doc = []
    if (children.$$typeof) { // 如果不是具名 直接放到数组里
      doc.push(children)
    } else {
      for (let item in children){ // 如果是具名,则便利children。把里面的内容放进数组里
        doc.push(children[item])
      }
    }
    return doc // 返回的是一个数组
  }
// 使用
render(){
    return (
  {this.compatibleNamed(this.props.children).map((item,index)=>{
            return (
                <div key={index}>
                  {item}
                </div>
            )
          })}
}
复制代码

高阶函数(柯里化)

高阶组件是一个工厂函数,它接收一个组件并返回另一个组件。

简单例子

function Child() {
  return (
      <div>
        child
      </div>
  )
}

// Cmp是组件  props 是组件的props
const foo = Cmp => props =>{
  return (
      <div style={{boder: '1px solid #ccc'}}>
        <Cmp {...props}/>
      </div>
  )
}

function APP() {
  const Foo = foo(Child)
  return (
      <div>
        <Foo/>
      </div>
  )
}
复制代码

修改以前Context的例子

创建 高阶组件

import { Consumer } from './Context';
import React from 'react';
//Cum 是组件 props是组件传递的props
const HOC = Cum => props => {
  return (
      <Consumer>
        { value => <Cum { ...props } { ...value }/> }
      </Consumer>
  )
}
export default HOC
复制代码

原来的 Context写法

import React, { Component } from 'react';
import ContextChild2 from './child2';
import HOC from './Hoc Hooks';
import {Consumer} from './Context';

class ContextChild extends Component {
  constructor(props) {
    super(props);
  }
  render() {
    return (
        <div>
          <h1>我是第一层</h1>
          <h2>{this.props.name}</h2>
          <Consumer>
            {
              value => <ContextChild2 {...value}/>
            }
          </Consumer>
        </div>
    )
  }
}
export default ContextChild
复制代码

改用HOC(高阶组件)之后

// child
import React, { Component } from 'react';
import ContextChild2 from './child2';
import HOC from './Hoc Hooks';
class ContextChild extends Component {
  constructor(props) {
    super(props);
  }
  render() {
    return (
        <div>
          <h1>我是第一层</h1>
          <h2>{this.props.name}</h2>
        {/*直接调用*/}
          <ContextChild2/>
        </div>
    )
  }
}
export default HOC(ContextChild)
// child2.js
import React, { Component } from 'react';
import HOC from './Hoc Hooks';
class ContextChild2 extends Component {
  constructor(props) {
    super(props);
  }
  render() {
    console.log(this.props)
    console.log('我是第二层的')
    return (
        <div>
          <h1>我是第二层的</h1>
          <h1>{this.props.name}</h1>
        </div>
    );
  }
}
export default HOC(ContextChild2)
复制代码

原本需要写Consumer现在只需要一个函数就解决了。非常方便

详细解释

可能还是会有点迷瞪。我在详细的说一次

import { Consumer } from './Context';
import React from 'react';

const HOC = Cum => props => {
  return (
      <Consumer>
        { value => <Cum { ...props } { ...value }/> }
      </Consumer>
  )
}
export default HOC
复制代码

Cum是组件,props是组件的所携带的参数,valueprovider所分发的数据.

类比一下下面的.就是把组件当作了参数,通过传入一个组件,返回另一个新的组件

<Consumer>
            {
              value => <ContextChild {...value}/>
            }
          </Consumer>
复制代码

完全修改后

// ContextFather.js

import React from 'react';
import ContextChild from './child';
import {Consumer,Provider} from './Context';

const data = {
  name: '我是father数据',
}

function ContextApp() {
  return (
      <div>
        <Provider value={ data }>
          {/*<Consumer>*/}
          {/*  {*/}
          {/*    value => <ContextChild {...value}/>*/}
          {/*  }*/}
          {/*</Consumer>*/}
          <ContextChild/>
        </Provider>
      </div>
  )
}
export default ContextApp
// child.js
import React, { Component } from 'react';
import ContextChild2 from './child2';
import HOC from './Hoc Hooks';
class ContextChild extends Component {
  constructor(props) {
    super(props);
  }
  render() {
    return (
        <div>
          <h1>我是第一层</h1>
          <h2>{this.props.name}</h2>
          <ContextChild2/>
        </div>
    )
  }
}
export default HOC(ContextChild)
// child2.js
import React, { Component } from 'react';
import HOC from './Hoc Hooks';
class ContextChild2 extends Component {
  constructor(props) {
    super(props);
  }
  render() {
    console.log(this.props)
    console.log('我是第二层的')
    return (
        <div>
          <h1>我是第二层的</h1>
          <h1>{this.props.name}</h1>
        </div>
    );
  }
}
export default HOC(ContextChild2)
复制代码

路由

安装

​ react-router包含3个库,react-router、react-router-dom和react-router-native。

​ react-router提供最 基本的路由功能,实际使用的时候我们不会直接安装react-router,而是根据应用运行行的环境选择安装

​ react-router-dom(在浏览器器中使⽤用)

​ react-router-native(在rn中使用)。

​ react-router-dom和 react-router-native都依赖react-router,所以在安装时,react-router也会⾃自动安装

npm install --save react-router-dom

基本使用

常用 BrowserRouter 、链接-Link、路由-Route、独占路由Switch、重 定向路由-Redirect

// RouterPage
import React, { Component } from 'react';
import { BrowserRouter, Link, Route,Switch } from 'react-router-dom';
import HomePage from './HomePage';
import UserPage from './UserPage';
import SearchPage from './Search';
import Login from './Login';
export default class RouterPage extends Component {
  render() {
    return (
        <div>
          <h1>RouterPage</h1>
          <BrowserRouter>
            <nav>
              <Link to="/">首页  </Link>
              <Link to="/user">用户中心  </Link>
              
              <Link to="/login">登陆  </Link>
            </nav>
            {/* 根路路由要添加exact,实现精确匹配 不加这个可能会出现多次渲染 */ }
            <Switch>
              {/*匹配到之后就不在继续往下匹配 Switch作用*/}
              <Route exact path="/" component={ HomePage }/>
              {/*<Route path="/user" component={ UserPage }/>*/}
              <Route path="/login" component={ Login }/>
              
              {/*404 页面 一定要放到最后*/}
              <Route component={() => <h1>404</h1>} />
            </Switch>
          </BrowserRouter>
        </div>
    );
  }
}
复制代码

动态路由

// RouterPage
<Link to="/search/123">详情  </Link>
<Route path="/search/:id" component={SearchPage} />
// Search
import React from 'react';
function SearchPage(props) {
  console.log(props) // 有三个 match history location
  return(
      <div>
      <p>通过这样获取到传递的参数</p>
        SearchPage{props.match.params.id}
      </div>
  )
}
export default SearchPage
复制代码

路由守卫(重点)

​ 案例:比如你需要在登陆之后才能进入到用户页面,这时候就需要用路由守卫。

熟悉Vue的同学应该能知道我想要表达的意思

//RouterPage
...
import RouterGuard from './RoutingGuard';
...
<Link to="/user">用户中心  </Link>
<Link to="/login">登陆  </Link>
<RouterGuard path ='/user' isLogin={true} Cmp={UserPage}/>
...
复制代码

RouterGuard(重点)

// RouterGuard
import React from 'react';
import { connect } from 'react-redux';
import { Route, Redirect } from 'react-router-dom';

function RouterGuard(props) {
  const { Cmp, path, isLogin, ...rest } = props
  console.log(rest) // 一些剩下的参数
  return (
      <Route
          { ...rest }
          render={ prop =>{
            console.log(prop) // 一些路由的参数
            return isLogin ? (<Cmp { ...prop } />) :
                (<Redirect to={ { pathname: '/login', redirect:props.location.pathname} }/>
                )} }
      >
      </Route>
  )
}
export default connect(
    state => {
      return {
        isLogin: state.user.isLogin,
      }
    },
)(RouterGuard)
复制代码

这里面主要是用了渲染函数 render, 还有一个三元表达式

其他页面代码

// LoginStore  记得用provider 包裹APP
import { createStore, applyMiddleware, combineReducers } from 'redux';
import logger from 'redux-logger';
import thunk from 'redux-thunk';

const initialLogin = { isLogin: false, name: null };
const userInfo = (state = {...initialLogin}, action) => {
  switch (action.type) {
    case 'getUser':
      return { ...state,isLogin: false }
    case 'LoginSuccess':
      return { ...state,isLogin: true,name: '阿琛' }
    case 'LoginFail':
      return { ...initialLogin }
    default:
      return state
  }
}
const store = createStore(
    combineReducers({
          user: userInfo
        }),
    applyMiddleware(logger, thunk))
export default store
// Login
import React from 'react';
import { connect } from 'react-redux';
import {Redirect} from 'react-router-dom'

function Login(props) {
  const {location,login,isLogin} = props
  console.log(props)
  const redirect = location.redirect?location.redirect:'/'
  if (isLogin){
    return  <Redirect to={redirect} />;
  }
  return (
      <div>
        <h1>LoginPage</h1>
        <button onClick={login}>登陆</button>
      </div>
  )
}
export default connect(state => ({
      isLogin: state.user.isLogin,
    }), {
      login: () => {
        return { type: 'LoginSuccess' };
      },
    },
)(Login)
复制代码

React全家桶

前面介绍了好多了,接下来介绍一些还没有介绍的

redux-saga

​ Redux-saga是Redux的一个中间件,主要集中处理react架构中的异步处理工作,被定义为generator(ES6)的形式,采用监听的形式进行工作。

作用和react-thunk差不多。都是为了解决异步

知识基础

Generato

Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函 数完全不同,详细参考参考阮一峰老师讲解

安装

npm install --save redux-saga

正在整理,明天就能发出来

以下的还没有学习或者整理,最近几天会抓紧时间整理出来

Dva

Umi

mobx

生命周期(暂无整理)

React HOOKs

整理不全,正在抓紧

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性

解决问题

  • 在组件之间复用状态逻辑很难,可能要用到render props和高阶组件,React 需要为共享状态逻辑提供更好的原生途径,Hook 使你在无需修改组件结构的情况下复用状态逻辑
  • 复杂组件变得难以理解,Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据)
  • 难以理解的 class,包括难以捉摸的this

注意事项

  • 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
  • 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用

useState

  • useState 会返回一对值:当前状态和它的函数,你可以在事件处理函数中或其他一些地方调用这个函数。它类似 class 组件的 this.setState,但是它不会把新的 state 和旧的 state 进行合并
  • useState 唯一的参数就是初始 state

计数器简单实例

import React,{useState} from 'react';

function Counter() {
    // useState会返回两个,一个是当前值。一个是改变它的函数
  let [state,setState] = useState({num: 0})
  let [num,setNum] = useState(0)
  // 两种方式 看个人喜好
  return (
      <div>
        <p>{state.num}</p>
        <p>{num}</p>
        <button onClick={()=>{setState({num: state.num +1})}}>+</button>
        <button onClick={()=>{setNum(num + 1)}}>+</button>
      </div>
  )
}
复制代码

每次渲染都是独立的闭包

  • 每一次渲染都有它自己的 Props and State
  • 每一次渲染都有它自己的事件处理函数
  • alert会“捕获”我点击按钮时候的状态。
// alter 捕获得是点击时候得状态
function Counter2 () {
  // useState 返回两个,一个是当前状态的属性,一个是修改状态的方法。
  let [state, setState] = useState({num: 0})
  let changeLater = ()=>{ // 这个alter 会捕获当前得状态。即点击按钮时候得状态
    setTimeout(()=>{
      alert(state.num)
    }, 2000)
  }
  return (
      <div>
        <p>{ state.num }</p>
        <button onClick={ () => setState({num: state.num+1}) }>+</button>
        <button onClick={ changeLater }>changeLater</button>
      </div>
  )
}
复制代码

函数式更新(解决上例的方法)

// 改变状态注意
function Counter3 () {
  // useState 返回两个,一个是当前状态的属性,一个是修改状态的方法。
  let [state, setState] = useState({num: 0})
  let lazyAdd = ()=>{ // 这个alter 会捕获 当前得状态。即点击按钮时候得状态
    setTimeout(()=>{
      setState({num: state.num+1})
    }, 2000)
  }
  let lazyAdd2 = ()=>{ // 这个alter 会捕获之后的状态。
    setTimeout(()=>{
      setState((state) => ({num: state.num+1}))
    }, 1000)
  }
  return (
      <div>
        <p>{ state.num }</p>
        <button onClick={ () => setState({num: state.num+1}) }>+</button>
        <button onClick={ lazyAdd }>lazyAdd</button>
        <p>如果这样写得话,会获取到点击时候得状态在进行改变</p>
        <button onClick={ lazyAdd2 }>lazyAdd2</button>
        <p>这样写,改变了整个state,就可以正常相加了</p>
      </div>
  )
}
复制代码

​ 如果新的 state 需要通过使用先前的 state 计算得出,那么可以将函数传递给 setState。该函数将接收先前的 state,并返回一个更新后的值

惰性state

  • initialState 参数只会在组件的初始渲染中起作用,后续渲染时会被忽略
  • 如果初始 state 需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用
// 惰性state
// initialState 初始状态参数只会在组件初始渲染得时候调用,后续被忽略
// 且不会自己整合state,需要自己整合
function Counter4 () {
  let [state, setState] = useState(function () {
    console.log(' 初始化')
    return {num: 0, name:'计算器'}
  })
  return (
      <div>
        <p>{state.num}</p>
        <button onClick={ () => setState({num: state.num+1}) }>+</button>
        <p>只会输出一次log</p>
        <p>React.StrictMode 模式下会输出两次log</p>
        <p>这是展示他不会整合state</p>
        <p>{ state.name }:{state.num}</p>
        <p>如果点击上面得button,则name会消失,下面这个则不会</p>
        <button onClick={ () => setState({...state, num: state.num+1}) }>+</button>
      </div>
  )
}
复制代码

按第一个button 计算器消失的原因: setState不会自己整合State,需要我们自己手动进行整合

这篇关于万字总结记录我的React学习路程(附各个代码参考)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!