React

229 阅读17分钟

参考链接 react.docschina.org/docs/gettin…

jsx

  1. 在 JSX {} 中嵌入表达式,即可以嵌入任何有结果的东西,譬如变量、方法、组件标签等。
  2. 用户定义的组件必须以大写字母开头,小写字母开头的元素代表一个 HTML 内置组件。
  3. 在运行时选择类型。
import React from 'react';
import { PhotoStory, VideoStory } from './stories';

const components = {
  photo: PhotoStory,
  video: VideoStory
};

function Story(props) {
  // 错误!JSX 自定义组件类型不能是一个表达式。  
  return <components[props.storyType] story={props.story} />;
  
  // 正确!JSX 自定义组件类型可以是大写字母开头的变量。  
  const SpecificStory = components[props.storyType];  
  return <SpecificStory story={props.story} />;
 }
  1. if 语句以及 for 循环不是 JavaScript 表达式,所以不能在 JSX 中直接使用。但是,你可以用在 JSX 以外的代码中。比如:
function NumberDescriber(props) {
  let description;
  if (props.number % 2 == 0) {   
    description = <strong>even</strong>;  
    } else {    
     description = <i>odd</i>;  
    }  
  return <div>{props.number} is an {description} number</div>;
}
  1. 属性展开
// 如果你已经有了一个 props 对象,你可以使用展开运算符...来在 JSX 中传递整个 props 对象。

// 以下两个组件是等价的:
function App1() {
  return <Greeting firstName="Ben" lastName="Hector" />;
}

function App2() {
  const props = {firstName: 'Ben', lastName: 'Hector'};
  return <Greeting {...props} />;
}
  1. 布尔类型、Null 以及 Undefined 将会忽略
// `false`, `null`, `undefined`, and `true` 是合法的子元素。但它们并不会被渲染。
// 反之,如果你想渲染 `false`、`true`、`null`、`undefined` 等值,你需要先将它们[转换为字符串]

// 以下的 JSX 表达式渲染结果相同:
<div />

<div></div>

<div>{false}</div>

<div>{null}</div>

<div>{undefined}</div>

<div>{true}</div>

react常用“指令”

  1. 展示变量, 格式如 { text } ;vue中{{ text }}
let text = '小码农'
return(<div> {text} </div> )
  1. 条件控制展示和隐藏
  • 2.1 与运算符 &&
let show = true
return({ show && <div>showDiv</div> } )
  • 2.2 元素变量
render() {
    const isLoggedIn = this.state.isLoggedIn;
    let button;
    if (isLoggedIn) {     
    button = <LogoutButton onClick={this.handleLogoutClick} />;    
    } else {      
    button = <LoginButton onClick={this.handleLoginClick} />;    
    }
    return (
      <div>
        <Greeting isLoggedIn={isLoggedIn} />        
        {button}      
      </div>
    );
  }
  • 2.3 三目运算符
render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    <div>
      {isLoggedIn ? <LogoutButton onClick={this.handleLogoutClick} />
        : <LoginButton onClick={this.handleLoginClick} />      }
    </div>);
}
  • 2.4 阻止组件渲染
function WarningBanner(props) {
  if (!props.warn) {return null;}
  return (
    <div className="warning">
      Warning!
    </div>
  );
}
  1. className, 格式如 className={styles.title} ; vue中 class=''
let show = true
return(<div className={ active ? `${styles.title} haha` : 'haha' } >hellow</div> )
  1. style, 格式如 style={{fontSize:'30px'}}
let styleObj = {fontSize: '30px' }
return(  
<div style={ styleObj }>
   <p style={{ fontSize: '20px' }}>123</p>
</div>)
  1. 遍历, 常用数组map,类似vue中v-for
let list = [1, 2, 3]
return(  
<div>
    <ul>
        {list.map( (item, index) => (<li key={index}>{ item }</li>) )}
    </ul>
</div> )
  • 5.1 正确的使用 key 的方式, Each child in an array or iterator should have a unique "key" prop.

function ListItem(props) {
  // 正确!这里不需要指定 key:  
  return <li>{props.value}</li>;
  }

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    // 正确!key 应该在数组的上下文中被指定    
    <ListItem key={number.toString()} value={number} />  );
  return (
    <ul>
      {listItems}
    </ul>
  );
}
  1. 事件绑定,格式如 onClick = {clickFun} ,vue中@click='clickFun',跟vue绑定事件不同,如 clickFun() 这样在react中初始化页面时就会自执行。
let clickFun = () => { console.log('click') }
return(
<div>
    <button onClick = { clickFun }>点击下</button>
</div> )
// 传参要如下方式实现
let otherclickFun = (params) => { console.log('click', params) }
return(
<div>
    <button onClick = { () => { otherclickFun('haha') } }>点击下,我还要传参</button>
</div> )

react 类式组件三大属性

  • state
  1. 不要直接修改 State,而是应该使用 setState():
  2. State 的更新可能是异步的,出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用。
  3. 另外setState对原state是合并操作,并不是整个替换操作。
  4. 往 state中的对象 或者 state中直接添加删除属性,页面能响应式;另外利用索引直接设置一个数组项时 或者 修改数组的长度时,页面也是响应式的。这点不同于vue2。
  5. 由于 shouldComponentUpdate其默认实现总是返回 true,让 React 执行更新。 所以只要调用了 setState(),组件默认会重新render。
<div id="example"></div>
<script type="text/babel">

class People extends React.Component {
  // state 会直接挂载到 People类 生成的 实例对象上
  state = {
    name: 'bwf',
    age: 18
  }
  // changeName 会直接挂载到 People类 生成的 实例对象上
  changeName = () => {
    // 箭头函数的this会指向定义时外层的this,而外层的this恰好是 People类 生成的 实例对象
    console.log('changeName', this)
    // 必须通过setState方式改变数据,才会引起页面响应式变化, 另外setState对原state是合并操作,并不是整个替换操作
    this.setState({ name: 'wmy' }, () => {
      console.log('setState---回调函数', this.state)
    })
    // setState之后立即打印数据,数据依旧是上次旧的值
    console.log('changeName---end', this.state)
  }

  render() {
    // render 和 构造器中的this 都会指向 People类 生成的 实例对象
    console.log('render', this)
    const { name, age } = this.state
    return (
      <div>
        <span onClick={this.changeName}>Hello---{name}---{age}</span>
      </div>
    )
  }
}
/**
1. 根据<People/> 会找到对应的组件
2. 由于People组件是由class定义的
   2.1 调用构造器会生成People类的实例
   2.2 实例会调用定义在People类原型对象上的render方法,生成虚拟DOM
   2.3 <People/>出现n次,构造器就会执行n次; render()执行的次数 =  <People/>出现的次数 + 状态改变的次数
   2.4 要让changeName方法中的this指向实例组件,通常有两种做法
       2.4.1 在构造器中使用bind
       2.4.2 使用表达式 + 箭头语法
**/
ReactDOM.render(
  <People />,
  document.getElementById('example')
);
	
  • props props在子组件中是只读的。因jsx的语法的灵活性,props的值可以是变量、组件、标签等`
1.类式组件props
//对传入props的类型限制的库
<script src="https://cdn.bootcss.com/prop-types/15.6.1/prop-types.js"></script>

<script type="text/babel">
class People extends React.Component {
  render() {
    // render 和 构造器中的this 都会指向 People类 生成的 实例对象
    console.log('render', this)
    // 1.父组件属性传值,都会自动被收集到 props对象身上。
    // 2.当然传入的标签体之间的结构会自动被收藏到props对象的children属性身上
    // 3.子组件也可以进行反结构,如 <div {...this.props}></div>
    const { name, age } = this.props
    return (
      <div>
        <span onClick={this.changeName}>Hello---{name}---{age + 1}</span>
      </div>
    )
  }
}
// 4.对传入props的类型限制, 当然这些也可以放到类式组件内,用 static 关键字声明
People.propTypes = {
  name: PropTypes.string.isRequired,
  age: PropTypes.number
};
// 对传入props的默认值进行设置
People.defaultProps = {
  age: 10
}
let userInfo = {
  name: 'bwf',
  age: 20
}

ReactDOM.render(
  <People {...userInfo} />,
  // 5.上面的写法是上面的语法糖
  //<People name={userInfo.name} age={userInfo.age}/>,

  document.getElementById('example')
);

</script>

2.函数式组件props
<script type="text/babel">
// 函数式组件
function People(props) {
  const { name, age } = props
  console.log('props', props)
  return (
    <div>
      <span>Hello---{name}---{age + 1}</span>
    </div>
  )

}
// 对传入props的类型限制
People.propTypes = {
  name: PropTypes.string.isRequired,
  age: PropTypes.number
};
// 对传入props的默认值进行设置
People.defaultProps = {
  age: 10
}

let userInfo = {
  name: 'bwf',
  age: 20
}
ReactDOM.render(
  <People {...userInfo} />,
  document.getElementById('example')
);

</script>
3.props实现插槽
// 1个插槽,props.children
function WelcomeDialog() {
  return (
    <FancyBorder color="blue">
     <p>hello</p>
    </FancyBorder>
  );

function FancyBorder(props) {
  return (
    <div className={'FancyBorder FancyBorder-' + props.color}>
      {props.children}    
    </div>
  );
}

// 多个插槽
function App() {
  return (
    <SplitPane left={<Contacts/>} right={<Chat/>}/>
  );
}

function SplitPane(props) {
  return (
    <div className="SplitPane">
      <div className="SplitPane-left">
        {props.left}      
      </div>
      <div>hello center</div>
      <div className="SplitPane-right">
        {props.right}      
      </div>
    </div>
  );
}
  • refs
1.字符串形式的ref
<input ref="inp1" type="text" defaultValue="4" />
// 获取到真实dom:自动会被收集到 refs 对象上 this.refs.inp1 
2.回调形式的ref
<input ref={node => this.input2 = node} type="text" defaultValue="3" />
// 获取到真实dom:this.input2
// 调用次数:行内式写法,每次初始化或者state变化时,回调函数都会执行,node先null后传为真实dom
3.React.createRef()形式的ref(最为推荐的方式)
input3 = React.createRef();
<input ref={this.input3} type="text" defaultValue="2" />
// 获取到真实dom:this.input3.current.value

类式组件中的this

<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
<script type="text/babel">
  class Count extends React.Component {
    constructor(props) {
      super(props)
      this.add2 = this.add2.bind(this)
    }
    state = {
      count: 0
    }
    //注意不是被实例对象直接调用的
    add = () => {
      //add 是被click事件驱动,但是add是箭头函数, 而箭头函数的this指向定义时环境中的this, 而class环境中的this指向Count的实例对象。
      console.log('this', this);
      let { count } = this.state
      this.setState({ count: ++count })
    }
    //注意不是被实例对象直接调用的
    add2() {
      /*
      add2 是被click事件驱动,但是add是普通函数, this在严格模式下指向undefined。 为了this能指向Count的实例对象,需要被强制绑定,使用bind
      bind 的操作放在构造器中,因为构造器中的this总是指向Count的实例对象。
      */
      console.log('this', this);
      let { count } = this.state
      this.setState({ count: ++count })
    }
    //注意add3此时是被实例对象直接调用的 
    add3() {
      //this指向调用它的实例对象
      console.log('this', this);
      let { count } = this.state
      this.setState({ count: ++count })
    }
    //注意add3此时是被实例对象直接调用的 
    add4 = () => {
      //箭头函数this指向定义它的环境
      console.log('this', this);
      let { count } = this.state
      this.setState({ count: ++count })
    }
    render() {
      const { count } = this.state
      return (
        <div>
          Count---{count}
          <div><button onClick={this.add}>add方式一</button></div>
          <div><button onClick={this.add2}>add方式二</button></div>
          <div><button onClick={() => { this.add3() }}>add方式三</button></div>
          <div><button onClick={() => { this.add4() }}>add方式四</button></div>
        </div>
      )
    }
  }
  // <Count/> 出现一次,它的实例对象就会被创建一次,而且是两个不同地址的对象,所以每份实例对象中的数据都是独立的。
  ReactDOM.render(<div><Count /> <hr /> <Count /></div>, document.getElementById("root"))
</script>

Fragment 包裹标签,类似vue中 template标签

<Fragment>
  <p>1</p>
  <p>2</p>
</Fragment>

祖孙间传值

  • 1.Provider 和 Consumer, 可以避免通过中间元素传递 props
//注意 Provider, Consumer 必须来自同一个 Context 下方可取到传递的数据
const Context = React.createContext();
const {Provider, Consumer} = Context;

// 祖组件 属性名必须为 value
<Provider value={{name:"wmy",age:18}}>
   <子组件/>
</Provider>

//孙组件 需要用到的地方 用 Consumer 包裹
<Consumer>
{ value => {return <p>name:{value.name}---age:{value.age}</p>}}
</Consumer>
    1. 通过props传递孙组件可以避免通过中间元素传递 props
//祖组件, 在祖组件中,直接把孙组件C作为中间组件B的属性传递
function A() {
  const user = { name: "bwf", age: 18 };
  const c = (<C user={user} />);
  return (
    <div>
      <h1>A组件---header</h1>
      <B c={c} />
      <p>A组件---footer</p>
    </div>
  );
}
//子组件
function B(props) {
  return (
    <div>
      <h1>B组件---header</h1>
      {props.c}
      <p>B组件---footer</p>
    </div>)
}
//孙组件
function C(props) {
  return (
    <div>
      <h1>C组件---header</h1>
      <p>A组件传递的数据:{props.user.name}</p>
    </div>)
}
ReactDOM.render(<A />, document.getElementById("example"))
  • 3.通过Provider和hooks里面的### useContext

解决父组件渲染,子组件就得无条件跟着渲染的性能问题

  • 1.类式组件中使用 React.PureComponent
const { PureComponent } = React
class Parent extends React.Component {
  state = {
    name: "bwf",
    age: 18,
    list: [11, 22, 33]

  }
  changeName = () => {
    this.setState({ name: "wmy" })
  }
  changeList = () => {
    const { list } = this.state;
    list.push(44)
    this.setState({ list: [...list] })
  }
  render() {
    console.log('Parent---render')
    const { name, age, list } = this.state
    return (
      <div>
        <h2>父组件</h2>
        <p>name:{name}</p>
        <p>age:{age}</p>
        <ul>
          {list.map((item, index) => <li key={index}>{item}</li>)}
        </ul>
        <button onClick={this.changeName}>change name</button>
        <button onClick={() => this.setState({ age: 20 })}>change age</button>
        <button onClick={this.changeList}>change list</button>
        {/* 子组件继承 PureComponent 后,只有当 初始创建或者name属性值(地址)改变时才会使Chldren子组件重新调用;
        特别注意,list的值是引用类型数据,所以默认情况下只有当list地址发生改变时,Chldren子组件才会render */}
        <Chldren name={this.state.name} list={list} />
      </div>
    )
  }
}
//子组件得继承 PureComponent
class Chldren extends React.PureComponent {
  render() {
    console.log('Chldren---render')
    return (
      <div>
        Chldren
        <p>传递:{this.props.name}</p>
      </div>
    )
  }
}
ReactDOM.render(<Parent />, document.getElementById("example"))
    1. 函数式组件使用 React.memo
const { useState } = React
function Parent() {
  console.log("Parent组件start")
  const [name, setName] = useState("")
  const [age, setAge] = useState(0)
  const [list, setList] = useState([11, 33])

  const changeList = () => {
    list.push(55)
    setList([...list])
  }
  return (
    <div>
      <h1>Parent组件</h1>
      <p>name:{name}</p>
      <p>age:{age}</p>
      <ul>
        {list.map((item, index) => <li key={index}>{item}</li>)}
      </ul>
      <button onClick={() => setAge((preState) => ++preState)}>changeAge</button>
      <button onClick={() => setName("bwf")}>changeName</button>
      <button onClick={changeList}>changeList</button>
      {/* 子组件被React.memo()包裹后,只有当 初始创建或者name属性值(地址)改变时才会使Chldren子组件重新调用;
    特别注意,list的值是引用类型数据,所以默认情况下只有当list地址发生改变时,Chldren子组件才会render */}
      <Bmo name={name} list={list}></Bmo>
    </div>
  )
}
function Children(props) {
  console.log("Children组件start")
  return (
    <div>
      <h1>Children组件</h1>
      <p>父组件传递的name:{props.name}</p>
    </div>
  )
}
const Bmo = React.memo(B)
ReactDOM.render(<Parent />, document.getElementById("example"))

pubsub-js 适合兄弟组件等传递数据,订阅消息一定要在发送消息之前注册,否则无法收到发送的消息。

import PubSub from 'pubsub-js'
export default class Button extends Component {
  changeListByPubSub = () => {
    // 发布消息
    PubSub.publish('demo', { pubSublists: [{ name: 'xxxx', age: 99, id: 99 }] })
  }
  render () {
    let { changeList } = this.props
    return (
      <div>
        button
        <button onClick={this.changeListByPubSub}>点击我增加列表</button>
      </div>
    )
  }
}

import React, { Component } from 'react'
import PubSub from 'pubsub-js'
import Button from '../Button'
export default class List extends Component {
  state = {
    pubSublists: []
  }
  componentDidMount () {
    // 组件挂载时 订阅消息,注意订阅消息一定要在发送消息之前注册,否则无法收到发送的消息
    this.subscribeId = PubSub.subscribe('demo', (id, stateDataObj) => {
      this.setState(stateDataObj)
    })
  }
  
  componentWillUnmount () {
    // 组件将要消亡时 取消订阅
    PubSub.unsubscribe(this.subscribeId)
  }
  render () {
    let { pubSublists } = this.state
    return (
      <div>
        <ul>
          {
            pubSublists.map(itemObj => (
              <li key={itemObj.id}>name:{itemObj.name}---age:{itemObj.age}</li>
            ))
          }
        </ul>
        <Button/>
      </div>
    )
  }
}


function A() {
  return (
    <div>
      <h2>A组件</h2>
      <B></B>
      <C></C>
    </div>
  )
}
function B() {
  // 由于B组件先created, 所以为了C组件能收到数据,所以让B组件mounted时晚点再发送消息给C组件
  useEffect(() => {
    PubSub.publish('BDATA', { name: "bwf" })
  }, [])
  return (
    <div>
      <h2>B组件</h2>
    </div>
  )
}
function C() {
  PubSub.subscribe('BDATA', (id, data) => {
    // { name: "bwf" }
    console.log('data', data);
  })

  return (
    <div>
      <h2>C组件</h2>

    </div>
  )
}
ReactDOM.render(<A />, document.getElementById("example"))

类式组件 vs 函数式组件

  • 类组件是基于面向对象编程的,它主打的是继承、生命周期等核心概念;而函数组件内核是函数式编程。
  • 类组件中可以获取到实例化后的 this,并且this 的工作方式,这与其他语言存在巨大差异。
  • 函数式组件,在 Hooks 的基础上提供了比原先更细粒度的逻辑组织与复用。
  • 性能优化上,类组件主要依靠 shouldComponentUpdate 阻断渲染来提升性能,而函数组件依靠 React.memo 缓存渲染结果来提升性能

Hooks

useState

  • 1.useState 状态管理, setState()设置时引起页面重新渲染的机制是 地址值发生改变。对于基本类型来说就是值改变,对于对象或数组等引用类型来说,就是前后地址值需要不一样,才会更新视图。
function Counter() {
  //const [某一个状态变量, 改变状态的方法] = useState('初始值')
  const [count, setCount] = useState(0);
  return (
    <div>
      Count: {count}
      <button onClick={() => setCount(0)}>Reset</button>
      //其中prevCount为count先前的值
      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
    </div>
  );
}

function List() {
  const [list, setList] = useState( [ ] );
  const addList = () => {
    list.push({ name: '张三' })
    //数组字面量写法:生成1个新的地址值。所以能引起页面的重新渲染
    setList([...list])
  }
  return (
   <div>
      <ul>
        {
          list.map((item, index) => (<li key={index}>{item.name}</li>) )
        }
      </ul>
      <button onClick={addList}>add LIST</button>
   </div>
  );
}
  • 2.setState()的参数可以是变量,也可以是一个函数,这个时候函数的默认参数是对应的状态的上一次的旧值
 <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
  • 3.惰性初始 state initialState 参数只会在组件的初始渲染中起作用,后续渲染时会被忽略。如果初始 state 需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用
const [state, setState] = useState(() => {
  const initialState = someExpensiveComputation(props);
  return initialState;
});
  • 4.hooks中的useState形式返回的setState VS 类式组件中的setState

  • 4.1. 类式组件中的setState,往 state中的对象 或者 state中直接添加删除属性,页面能响应式;另外利用索引直接设置一个数组项时 或者 修改数组的长度时,页面也是响应式的。但是hooks中的setState设置时引起页面重新渲染的机制是引用类型的地址值发生改变基本类型的值发生改变

  • 4.2. 出于性能考虑, 类式组件中的setStatehooks中的setState 的更新都可能是异步的,React可能会把多个 setState() 调用合并成一个调用。

const { useState } = React
console.log(React)
class App extends React.Component {
 state = {
   info: { age: 9999 }
 }
 changeInfo = () => {
   // App是1个类式组件,setState时info虽然是同1个地址,但是页面能响应式变化
   const { info } = this.state
   info.age = 10000
   this.setState({ info })
 }

 render() {
   console.log('A---render')
   const { info } = this.state
   return (
     <div>
       <p>age:{info.age}</p>
       <button onClick={this.changeInfo}>change</button>
       <B />
     </div>
   )
 }
}

const B = () => {
 console.log('B---start')
 const [user, setUser] = useState({})
 const changeUser = () => {
   user.name = "bbb"
   // setUser(user) 页面不会响应式
   setUser({ ...user })
 }
 return (
   <div>
     <p>name:{user.name}</p>
     <button onClick={changeUser}>changeUser</button>
   </div>
 )
}
ReactDOM.render(
 <App />,
 document.getElementById('example')
);

useEffect

  • 1.一个组件中,可以提供多个useEffect,用法 useEffect(fn, 可选).常用于开启定时器 订阅一些websocket等服务。

  • 2.当第二个参数没有时,类似vue中updated 钩子。运行次数:初始化1次 + 组件状态改变时 n 次。

// 组件状态值有变都会执行
let timeId = null
useEffect(() => {
  timeId = setInterval(()=>{
     console.log('timeId', 11);
   },1000)
   //这个返回 函数 相当于beforeUnmount
  return () => {
    clearInterval(timeId)
   }
});
  • 3.当第二个参数为空数组时,类似vue中mounted。运行次数:初始化1次。
useEffect(() => {}, [ ] );
  • 4.当第二个参数数组不为空时,类似vue中watch。运行次数: 初始化1次 + 依赖者发生变化时n次。
// 在 mount 和 name, age 值变化时运行
let timeId = null
useEffect(() => {
  
}, [name, age] );

useContext 祖先组件可以跨中间组件向后代传递数据

const { useState, useContext } = React
//提供和接收数据的必须是同1个context
const UserContext = React.createContext();
function App() {
  const [user, setUser] = useState({ name: "wmy" })
  return (
    <div>
      <h2>App组件</h2>
      <button onClick={() => { setUser({ name: "@@@" }) }}>change user</button>
      <UserContext.Provider value={user}>
        <B />
      </UserContext.Provider>
    </div>
  );
}
function B() {
  return (
    <div>
      <h2>B组件</h2>
      <C />
    </div>
  );
}
function C() {
  // 提供者的value的值
  const user = useContext(UserContext);
  return (
    <div>
      <h2>C组件</h2>
      <p>接收App组件传递的数据:{user.name}</p>
    </div>
  );
}
ReactDOM.render(<App />, document.getElementById("example"))

useMemo

const { useState, useCallback, useEffect, useMemo } = React
const Parent = () => {
  const [firstName, setFirstName] = useState('王')
  const [lastName, setLastName] = useState('小红')
  // 初始化时会执行fn函数,另外当依赖firstName 或者 lastName改变时,也会执行。
  const fullName = useMemo(() => firstName + "@@@" + lastName, [firstName, lastName])
  return (
    <div>
      fullName---{fullName}
      <button onClick={() => setFirstName("张")}>change firstName</button>
      <button onClick={() => setLastName("三丰")}>change lastName</button>
    </div>
  )
}
ReactDOM.render(<Parent />, document.getElementById("example"))

useCallback useCallback(fn, deps) 相当于 useMemo(() => fn, deps)。

useRef 获取dom,注意dom渲染完成再获取,否则存在null情况。

const useRefFun = () => {
 const inp = useRef(null)
 const onClick = ()=>{
  //  inp.current 获取当前dom对象
   const inpObj = inp.current
   console.log('defaultValue---id', inpObj.defaultValue, inpObj.id);
 }
  return(
    <div>
      <input ref={inp} defaultValue="nihao" id='1'/>
      <button onClick={onClick}>获取input值</button>
    </div>
  )
}

初始化简单的react项目

1. 全局安装包:npm i -g create-react-app 
2. 使用命令生成项目: create-react-app 项目名称
3. 启动项目

react cdn

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="https://cdn.staticfile.org/react/16.13.1/umd/react.development.js"></script>
  <script src="https://cdn.staticfile.org/react-dom/16.13.1/umd/react-dom.development.js"></script>
  <script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
</head>

<body>
  <div id="example"></div>
  <script type="text/babel">
    const { useState, useCallback, useEffect, useMemo } = React
    function Parent() {
      return (
        <div>
          <h2>Parent组件</h2>
        </div>
      )
    }
    ReactDOM.render(<Parent />, document.getElementById("example"))
  </script>
</body>
</html>

样式的模块化

为了避免类名相同的样式覆盖问题,使用样式模块化
//1. 样式文件命名时,加入module, 如 index.module.css
//2. 引入时, import demo from './index.module.css' (所有类名会自动变成demo对象的属性)
//3. 使用 <div className={demo.head}></div>

配置代理

// 方式1: package.json 中,"proxy": "服务器 ip:端口"
// 方式2: setupProxy.js(src目录新建这个文件)
const proxy = require("http-proxy-middleware")
module.exports = function (app) {
  app.use(
    proxy('/api1',
      {
        target: '服务器1 ip:端口',
        changeOrigin: true,
        pathRewrite: { '^/api1': '' }
      }),
    proxy('/api2',
      {
        target: '服务器2 ip:端口',
        changeOrigin: true,
        pathRewrite: { '^/api2': '' }
      }),
  )
}

路由

  • BrowserRouter/HashRouter、Link/NavLink、Route
/*
在App组件 加 BrowserRouter 包裹,主要是为了整个应用是一个路由库管理,否则会有意料之外的结果
*/
ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>
  ,
  document.getElementById("root")
);

<p><Link to="/home">Link:home</Link></p>
<p><Link to="/button">Link:button</Link></p>
{/* <NavLink to="/home" activeClassName="demo-active__link">NavLink:home</NavLink> */}
{/* 加 Switch 主要是为了匹配上一个Route之后不再匹配后面的*/}
<Switch>
{/* 将匹配到的组件显示在页面上 */}
  <Route path='/home' component={Home}></Route>
  <Route path='/button' component={Button}></Route>
  <Redirect to='/home' />
</Switch>
  • 一般组件和路由组件
路由组件: <Route path='/home' component={Home}></Route>(Home就是路由组件)
路由组件props能收到 
history:{go:fn, goBack:fn, goForward:fn, push:fn, replace:fn}   
location:{pathname:"", search:"", state: } 
match:{params:{}, path:"", url:""}
  • 解决BrowserRouter样式丢失问题
场景复现:当出现多级路由且使用 BrowserRouter,当刷新页面时,有时会出现样式等文件路径请求不到
1. 把样式文件 ./ 路径改为 /, <link rel="stylesheet" href="/css/base.css">
2. 使用 %PUBLIC_URL%, <link rel="stylesheet" href="%PUBLIC_URL%/css/base.css">, 以public下的绝对路径
3. 使用 HashRouter
  • 路由的模糊匹配和精准匹配
1.默认模糊匹配: 
<p><Link to="/home/a">Link:home</Link></p>
以上路由能模糊匹配以下组件
<Route path='/home' component={Home}></Route> 
<Route path='/home/a' component={Home}></Route>

2.精准匹配:exact
<p><Link to="/home/a">Link:home</Link></p>
以上路由仅能精准匹配以下组件
<Route exact path='/home/a' component={Home}></Route>
 
3.不要轻易使用 exact
  • Redirect 重定向策略
放到 Route 组件最后面,当所有注册路由组件都无法匹配上时,会采用重定向策略。
<Redirect to="/demo">
  • 嵌套路由(多级路由)
路由的匹配是由 注册顺序依次进行匹配的,也就是从根组件,次根组件等依次匹配。
注册子路由的时候 path 需要带上父路由的路径
譬如 
App组件:
<p><Link to="/home">Link:home</Link></p>
<Route path='/home' component={Home}></Route>

Home组件中的子组件 记得LinkRoute 一定要带上 /home
<p><Link to="/home/demo">Link:home</Link></p>
<Route path='/home/demo' component={Demo}></Route>
  • 路由传参
1. params参数
//wmy 18是路由跳转时携带的参数
<Link to={`/demo/wmy/18`}>demo</Link>

<Route path='/demo/:name/:age' component={Demo}></Route>

//Demo 路由组件接收
props.match.params   {name:"wmy", age:"18"}

2. search参数
<Link to={`/demo?name=wmy&age=18`}>demo</Link>

<Route path='/demo' component={Demo}></Route>

//Demo 路由组件接收,可以使用 react自带的库 querystring 对象中parse方法进行处理
props.location.search   ?name=wmy&age=18
querystring.parse(props.location.search.slice(1))   {name:"wmy", age:"18"}
3. state参数( 当路由模式为 BrowserRouter 模式时,刷新页面,state也可以保留住参数)
<Link to={{pathname:'/demo', state:{name:"wmy", age:"18"}}>demo</Link>

<Route path='/demo' component={Demo}></Route>

// 当路由模式为 BrowserRouter 模式时,刷新页面,state也可以保留住参数
props.location.state {name:"wmy", age:"18"}
  • 编程式导航
//携带 params 参数
props.history.push('/demo/wmy/18')
//携带 search 参数
props.history.push('/demo?name=wmy&age=18')
//携带 state 参数
props.history.push('/demo', {name:"wmy", age:18})
// 注意他们接收参数方式和非编程式导航一样


//拓展
props.history.goBack() // 返回到上一次记录
props.history.goForward() // 前进到下一次记录
props.history.go(n) //n:步数  跳跃
  • withRouter
// 让非路由组件也可以拥有像路由组件的Api,即 props能收到 history/location/match属性
import {withRouter} from 'react-router-dom'

export default withRouter(Demo)

//以上两步即可让 Demo 组件拥有操作路由Api
  • BrowserRouter 对比 HashRouter
1.使用 BrowserRouter 浏览器刷新对于路由state参数没有影响,
而若使用 HashRouter,刷新后state会清空;
2.使用 HashRouter 可以解决一些部署后路径报错相关问题,#后面的内容 不会发送请求服务器
  • 路由的懒加载 lazy 和 Suspense
// 使用路由懒加载可以提高首页的响应速度
import {lazy} from 'react'

import Loading from './Loading'
const Home = lazy(()=>import('./Home'))
const User = lazy(()=>import('./User'))


<p><Link to="/home">Link:home</Link></p>
<p><Link to="/user">Link:user</Link></p>

// 注意 Loading 组件得正常引入,不要懒加载
<Suspense fallback={ <Loading/>}>
   <Route path='/home' component={Home}></Route>
   <Route path='/user' component={User}></Route>
</Suspense>

项目目录

  • index.js
import React from 'react';
import { createRoot } from 'react-dom/client';
import { Providers } from 'store/providers'.
import App from './App';

const container = document.getElementById('root');
const root = createRoot(container);

root.render(
  // <React.StrictMode>
  <Providers>
    <App />
  </Providers>
  // </React.StrictMode>
);
  • App.js
import * as React from "react";
import { RouterProvider, } from "react-router-dom";
import router from "./router";
import Loading from './components/Loading';

export default function App() {
  return <RouterProvider router={router} fallbackElement={<Loading />} />;
}
  • router.js
import { Suspense } from 'react'
import { createBrowserRouter, redirect,} from "react-router-dom";
import React from "react";
import Loading from './components/Loading';
import ErrorBoundary from './components/errorBoundary';

import NotFound from './not-found';
const IndexPage = React.lazy(() => import('app/indexPage/page'))
const PublicDetailPage = React.lazy(() => import('app/PublicDetailPage/page'))

let router = createBrowserRouter([
  {
    path: "/",
    element: <Suspense fallback={<Loading />}>
      <IndexPage />
    </Suspense>,
  },
  {
    path: '/public-detail',
    element: <Suspense fallback={<Loading />}>
      <PublicDetailPage />
    </Suspense>,
    errorElement: <ErrorBoundary />
  },
  {
    path: '*',
    Component: NotFound
  }
]);

react-redux @reduxjs/toolkit

redux-toolkit.js.org/tutorials/q…

  • src/store/index.js
import { configureStore, } from '@reduxjs/toolkit'
import { reducer } from './rootReducer'
// import { middleware } from './middleware'

export const reduxStore = configureStore({
  reducer,
//   middleware: (getDefaultMiddleware) => {
//     return getDefaultMiddleware().concat(middleware)
//   },
})
  • src/store/providers.js
import { Provider } from 'react-redux'

import { reduxStore } from './index'

export const Providers = (props) => {
  return <Provider store={reduxStore} >
    {props.children}
  </Provider>
}
  • src/store/rootReducer.js
import { privateDetail } from './slices/privateDetail'

export const reducer = {
  privateDetail: privateDetail.reducer,
}
  • src/slices/privateDetail/index.js
import { createSlice } from "@reduxjs/toolkit";
import { fetchfundDetail, } from './api'

export const privateDetail = createSlice({
   name: 'privateDetail',
   initialState: {
    fundDetail: {},
   }
   reducers: {
    setDetail: (state, { payload }) => {
      state.fundDetail = payload;
    },
   }
})

export const { setDetail } = privateDetail.actions


export const getPrivateDetailFundDetail = (payload) => {
  return async (dispatch) => {
    try {
      const response = await fetchfundDetail(payload);
      dispatch(setDetail(response || {}));
    } catch (e) {
      console.log(e);
    } finally {
    }
  }
}
  • page.js
import { getPrivateDetailFundDetail } from 'store/slices/privateDetail/index.js'
import { useDispatch, useSelector } from 'react-redux'
import { useEffect, useMemo  } from 'react'

const PrivateDetail = () => {
  const dispatch = useDispatch();
  const { fundDetail } = useSelector(state => state.privateDetail)

  useEffect(() => {
    dispatch(getPrivateDetailFundDetail({ fundCode, uniqueCode }))}, []
   )

  return <div>
           {fundDetail.orgName}
         </div>
 }

4-2管理员流程图.png