react教程

第一章:基础知识

1、hello world

一个简单的hello world

<!DOCTYPE html>
<html lang="en">
<body>
    <div id="container"></div>
    <script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
    <script>
        const LikeButton = React.createElement('button',[], '按钮'); //React 的 createElement 来创建 React DOM
        ReactDOM.render(LikeButton, document.querySelector('#container')); //渲染到指定元素中
    </script> 
</body>
</html>

jsx替代createElement来创建React DOM

因为createElement方法创建一个元素太复杂麻烦,所以jsx就应运而生。
JSX 是一种 JavaScript 的语法糖,最终编译出来被本质还是执行了createElement方法。Facebook 开发JSX出来,主要用于 React 中。虽然 JSX 的内容会长得像 html,但还是 JavaScript。

<!DOCTYPE html>
<html lang="en">
<body>
    <div id="container"></div>
    <script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
    <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
    <script type="text/babel">
        const LikeButton = <button>按钮 </button>;//jsx创建React Dom
        ReactDOM.render(LikeButton, document.querySelector('#container')); //渲染到指定元素中
    </script> 
</body>
</html>

使用本地babel环境

这种引入babel在线编译方式适合于学习和创建简单的示例。然而,它会使你的网站变慢,并不适用于生产环境
目录结构如下

index.html

<!DOCTYPE html>
<html lang="en">
<body>
    <div id="container"></div>
    <script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
    <script src="TestButton.js"></script> <!-- 加载我们的 React 组件。-->
    <script>
        ReactDOM.render(LikeButton, document.querySelector('#container')); //渲染到指定元素中
    </script> 
</body>
</html>

src>TestButton.js

//创建React Dom
const LikeButton = <button>按钮</button>;

环境准备
运行npm init -y 来用webpack初始化项目
执行npm install babel-cli@6 babel-preset-react-app@3来安装本地解析jsx的babel环境
本地编译
执行npx babel --watch src --out-dir . --presets react-app/prod
然后就会发现,项目根目录下,多了了一个编译后的文件,内容如下

//创建React Dom
var LikeButton = React.createElement(
  "button",
  null,
  "\u6309\u94AE"
);

使用脚手架

使用官方出得自动化项目构建工具,可以更加迅速的搭建项目。脚手架内置了完整的一套项目所需的工具链,这些工具链我们可以自己搭配,但是过于繁琐,所以直接使用就行。具体使用说明参考脚手架的官方文档

安装脚手架工具

npm install -g create-react-app

使用脚手架创建项目

create-react-app hello-world

本地开发

npm start

编译打包,部署服务器

//当你准备好部署到生产环境时,执行此命令将会在`build`文件夹内生成你应用的优化版本
npm run build

参考

https://www.jianshu.com/p/42a3ec621e94

2、目录分析

脚手架创建新建项目后目录

删除无关紧要的文件

这些东西,目前来说无关紧要,以后会单独拿出来一个一个讲

  1. app.css 测试组件的样式,无关紧要
  2. app.test.js 自动化的测试文件
  3. logo.svg 测试组件内容的图片文件
  4. gitignore git相关文件
  5. serviceWorker.js 移动端开发的,PWA必用,用于离线浏览

删除完成后,项目会报错,这是因为项目刚才那些文件没了,依赖拿不到,根据提示,删除这些引用即可。
修改入口文件 src>index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));

修改测试文件内容,使其简单点src>App.js

import React from 'react';
function App() {
    return (hello,world);
}
export default App;

查看项目,是不是项目的文件就没几个了

启动项目

运行 npm tart

3、代码分析

项目启动

npm start
先会编译代码后,然后并启动一个本地服务,运行编译后的代码
本质上就是启动了一个node服务,查看node_modules/react-scripts/scripts/start.js 查看源码即可看到:

devServer.listen(port, HOST, err => {
    ...
})

有人说是调用webpack启动服务,也没错,因为webpack-dev-server也是一个小型的Node.js Express服务器,本质还是node

代码分析

index.js

import React from 'react'; //react核心包
import ReactDOM from 'react-dom';//react关联包
import './index.css'; //引入全局样式
import App from './App'; //引入测试组件
ReactDOM.render(<App />, document.getElementById('root'));

该文件为项目的入口文件,代码最后一行是将模板转为 HTML 语言,并插入指定的 DOM 节点,即将的内容渲染到项目的根元素“#root”中去,其中<App/>为根组件。

app.js

import React from 'react';
function App() {
    return (hello,world);
}
export default App;

用了jsx语法构建组件
为什么(函数)组件中都没有用到react也需要引入 React包呢?
答案就是,我们的JSX语法只是一种语法糖,它最终会被转译成纯粹的js语法,因此在webpack调用babel转译之后,我们的代码就变成了:

var App = function App() {
  return React.createElement(
    "div",
    null,
    "Hello World!!!"
  );
};

这里出现了React.createElement,这就是为什么我们需要在函数式组件开头引入React的原因

参考

https://www.jianshu.com/p/63f0e2ac8aea

4、组件

什么是组件

组件是View的重要组成部分,每一个View页面都由一个或多个组件构成,可以说组件是React应用程序的基石。

核心概念

组件有两个核心概念:props、state,前者用于定义外部接口,后者则用于纪录内部状态。
然后通过这两个属性的值,在render方法里面生成并返回这个组件对应的 HTML 结构
React的组件简单理解起来其实就是一个函数,这个函数会接收propsstate作为参数,然后进行相应的逻辑处理,最终返回该组件的虚拟DOM展现。

写个demo

在组件构成中,按状态分可分有状态组件(类组件、createClass)和无状态组件(函数组件)

函数式组件

function App() {
  return <div className="App">hello,world</div>;
}

使用一个纯函数来创建这样的组件,也是组件最简单的写法,功能却少的可怜。
函数组件中,你无法使用State,也无法使用组件的生命周期方法,这就决定了函数组件都是展示性组件,接收Props,渲染DOM,而不关注其他逻辑。

类组件

某种程度上来说,react的热捧反而推动了es规范中class的制定,就是因为类组件的存在

class App extends React.Component{
  render(){
    return <div className="App">hello,world</div>;
  }
}

类组件有更多额外的功能,如组件自身状态生命周期钩子

es5创建组件
React.createClass是react刚开始推荐的创建组件的方式,这是ES5的原生的JavaScript来实现的React组件,是有状态的组件。但是创建过程过于繁琐,且还有性能问题,所以已经不再推荐使用

后话

后来的hook却改变了这种情况,hook使得函数式组件也有了自己的状态和钩子函数。那么既然类组件都已经有了这些,为什么还要发明hooks呢?愿意大致如下:

  • 前端开发人员对基于class的oop比较陌生,extends、this往往弄的晕头转向
  • 难以重用和共享组件中的与状态相关的逻辑
  • 逻辑复杂的组件难以开发与维护,当组件需处理多个互不相关的 local state 时,每个生命周期函数中可能会包含着各种互不相关的逻辑在里面
  • 由于业务变动,函数组件不得不改为类组件等等

考点

组件作用?

  • 易维护:模块化开发,代码解耦。
  • 可复用:每个组件都是具有独立功能的,它可以被使用在多个UI场景

5、状态

什么是状态

React 把组件看成是一个状态机。通过与用户的交互,实现不同状态,然后渲染 UI,让用户界面和数据保持一致。
在React 里,只需更新组件 state,新 state 会自动引起重新渲染用户界面,而不需要要操作 DOM。

写个demo

类组件实现

class App extends React.Component{
  constructor(){
    this.state={
      count:0
    };
  }
  increment = () =>{
    const count = ++this.state.count;
    this.setState({count})
  }
  render(){
    return (
      <div className="App">
        <div>{this.state.count}</div>
        <button onClick={this.increment}>自增</button>
      </div>
    );
  }
}

函数组件实现

函数式组件本身没有自己的状态,这里用到了hook的useState实现

import React,{ useState } from 'react';
function App(){
  let [count,setCount] = useState(0);
  
  const increment = ()=>{
    setCount(++count);
  };
  return (
    <div className="App">
      <div>{count}</div>
      <button onClick={increment}>自增</button>
    </div>
  );
}

考点

  • setState方法会自动合并你更新的数据到state中,useState不会
  • setState是异步更新

6、属性

什么是属性

考虑到组件的复用性,多个地方使用同一个组件,组件内具体的数据,是由调用者的外部数据决定的,通过属性来接受调用者的参数。react组件是一个状态机,props的改变同样也会触发重渲。

单向数据流 && 属性不可变

React中数据流是单向由父节点流向子节点,如果父节点的props发生了改变,那么React会递归遍历整个组件树,重新渲染所有使用该属性的子组件。具体的话第二章‘状态管理’有专题。
state 和 props 主要的区别在于props是不可变的,而 state 可以根据与用户交互来改变。
这种限制也是为了避免双向流动导致逻辑不清,比如难以追踪到底是哪里对数据进行操作。

写个demo

一般项目是这样布局的

ShowName.js(写法1:函数组件写法)

function ShowName(props){
  return  <div>你好,我叫:{props.name}</div>;
}

ShowName.js(写法2:类组件写法)

class ShowName extends React.Component{
  render(){
    return  <div>你好,我叫:{this.props.name}</div>;
  }
}

App.js

import ShowName from '../components/ShowName'
function App(){
  return (
    <div className="App">
        <ShowName name='丁少华'/>
        <ShowName name='阎涵'/>
    </div>
  );
}

7、钩子

生命周期函数(也叫钩子函数)是在某个阶段给你一个做某些处理的机会的回调函数。比如在组件预备、创建、使用和销毁的过程中的函数监听。

都有哪些?

组件的生命周期可分成三个状态:

  • Mounting:已插入真实 DOM
  • Updating:正在被重新渲染
  • Unmounting:已移出真实 DOM

渲染前

componentWillMount

首次渲染后

componentDidMount
在第一次渲染后调用
组件已经生成了对应的DOM结构,可以通过this.getDOMNode()来进行访问。
如果你想和其他JavaScript框架一起使用,
可以在这个方法中调用setTimeout, setInterval或者发送AJAX请求等操作
防止异步操作阻塞UI

接收到新prop时

componentWillReceiveProps
在组件接收到一个新的prop更新后被调用。
初始化时不会被调用

接收到新props或state

shouldComponentUpdate
在组件接收到新的props或者state时被调用。
使用forceUpdate不被调用
初始化时时不被调用

接收到新props或state,但还未render

componentWillUpdate
在组件接收到新props或state但还未render时被调。
在初始化时不会被调用

组件更新后

componentDidUpdate
在组件完成更新后立即调用。
在初始化时不会被调用

组件从 DOM 中移除之前

componentWillUnmount
在组件从 DOM 中移除之前立刻被调用。

hook

以上的钩子函数都是类组件中,方可使用的。但是hook使得函数组件也能够拥有钩子函数

这里只说一下类组件中存在对应的那些hook函数,具体hook知识会单独拿出来讲

import React,{ useEffect } from 'react';
function App(){
  const updateScroll = (e) => {
    console.log('滚动了')
  };
  useEffect(() => {
    //监听滚动条滚动 相当于componentDidMount
    window.addEventListener('scroll', updateScroll);
    return () => {
      //移除滚动条滚动的监听 相当于componentWillUnmount
      window.removeEventListener('scroll',updateScroll);
    }
  }, []); 
  //此数组,里边定义监听哪些props或state,这些一变化useEffect就会被调用
  //若不写,effect不依赖于props或state中的任何值,所以它只会执行一次
  return (
    <div className="App">
        Hello.React
    </div>
  );
}

最后一个数组为空的话,相当于componentDidMount和componentWillUnmount
如果有了的话,相当于其它几个

8、其它

列表渲染

function App(){
  const numbers = [1, 2, 3, 4, 5];
  return (
    <div className="App">
      <ul>
        {numbers.map((numbers) =>
          <li key={numbers}>序号:{numbers}</li>
        )}
      </ul>
    </div>
  );
}

条件渲染

可以用三目、&&、或者{自执行函数表达式}等等吧

function App(){
  return (
    <div className="App">
      1>0
      {1>0?<span>是对的,</span>:<span>是错的,</span>}
      {1>0 && <span>真的是对的哦,</span>}
      {(()=>{
        if(1>0){
          return <span>我觉得对</span>
        }else{
          return <span>我觉得不对</span>
        }
      })()}
    </div>
  );
}

与后端交互

推荐使用第三方类库axios或者新原声api之fetch

Refs

React 支持一种非常特殊的属性 Ref ,你可以用来绑定到 render() 输出的任何组件上。通过ref获取组件实例,进而获取相关数据或者操作dom等

表单

在 HTML 中,表单元素如input、 textarea 和 select之类通常自己维护 state,并根据用户输入进行更新。而React 中,可变状态通常保存在组件 state 属性中,并且只能通过使用 setState()来更新。
我们可以把两者结合起来,使 React 的 state 成为“唯一数据源”。
渲染表单的 React 组件还控制着用户输入过程中表单发生的操作(事件)。
被 React 以这种方式控制取值的表单输入元素就叫做 受控组件

class App extends React.Component{
  constructor(props){
    super(props);
    this.state={
      count:0 //唯一数据源
    };
  }
  iptChange = ({target:{value}}) =>{ //控制着用户输入操作
    this.setState({count:value}) 
  }
  render(){
    return (
      <div className="App">
         {/* 这就是一个受控组件:以这种方式控制取值的表单输入元素 */}
         <input onChange={this.iptChange} value={this.state.count}/>
      </div>
    );
  }
}

react理念

React的核心思想可总结为UI=render(data),data是数据源,render是react提供的纯函数,在render函数确定的情况下用户界面的展示完全取决于数据源[state、props]。 把每一个组件都看成是一个状态机,组件通过内部state和外部props,来维护组件状态的变化。

第二章:高级进阶

1、状态管理-简介

近两年前端技术的发展如火如荼,大量的前端项目都在使用或转向 Vue 和 React 的阵营
由前端渲染页面的单页应用占比也越来越高
这就代表前端工作的复杂度也在直线上升,前端页面上展示的信息越来越多也越来越复杂。
我们知道,任何状态都需要进行管理,那么react是如何状态管里的呢?
react是由内部状态state+外部状态props来驱动视图,state和props作为唯一的react数据源,只要发生改变,就会引起视图的更新。即典型的数据驱动视图型

2、状态管理-数据流

单向绑定 vs 双向绑定

单双向绑定,指的是View层和Model层之间的映射关系。

react采取单向绑定,如图所示:

用户访问View,用户发出交互到Actions中进行处理,Actions中通过setState对State进行更新,State更新后触发View更新。可以看出,View层不能直接修改State,必须要通过Actions来进行操作,这样更加清晰可控

vue支持单向绑定和双向绑定

  • 单向绑定:插值形式{{data}},v-bind也是单向绑定
  • 双向绑定:表单的v-model,用户对View层的更改会直接同步到Model层

实际上v-model只是v-bind:value 和 v-on:input的语法糖,我们也可以采取类似react的单向绑定。
vue的v-model在操作表单是,显得很简单,我们不用去写繁琐的onChange事件去处理每个表单数据的变化,但是双向绑定也会导致数据变化不透明,不清晰可控。优点和缺点共存,有时候一个人的优点也是一个人的缺点,道理都是相通的。

单向数据流 vs 双向数据流

数据流指的是组件之间的数据流动。

vue和react仅单向数据流

虽然vue有双向绑定v-model,但是vue和react父子组件之间数据传递,仍然还是遵循单向数据流的,父组件可以向子组件传递props,但是子组件不能修改父组件传递来的props,子组件只能通过事件通知父组件进行数据更改,如图所示:

优点是所有状态的改变可记录、可跟踪,源头易追溯; 所有数据只有一份,组件数据只有唯一的入口和出口,使得程序更直观更容易理解,有利于应用的可维护性

angularJs可以双向数据流

相比之下,元老级框架AngularJS却不一样,它允许在子组件中直接更新父组件的值

数据流与绑定

准确来说两者并不是一回事。单向数据流也可有双向绑定,双向数据流也可以 有双向绑定
但是很多资料混为一谈

3、状态管理-组件通讯

上一节说,react是单向数据流,即通过props来向下通信的,接下来学习组件通信的专题知识。

原生办法

父子通信

// 父组件
function App(){
  return (
    <div className="App">
        <ShowName name='丁少华'/>
    </div>
  );
}
// 子组件
function ShowName(props){
  return  <div>你好,{props.name}</div>;
}

子父通信
通过传递函数

// 父组件
function App(){
  const getChildMsg = (msg)=>{
      console.log('接收到子组件的通知:'+msg)
  };
  return (
    <div className="App">
        <ShowName name='丁少华' getChildMsg={getChildMsg}/>
    </div>
  );
}
// 子组件
function ShowName(props){
  const senMsgToParent = ()=>{
    props.getChildMsg('你好,我是子组件')
  };
  return (
    <div>
      你好,{props.name}<br/>
      <button onClick={senMsgToParent}>给父组件发送通知</button>  
    </div>
  );
}

兄弟通信
在两个组件外层嵌套一个父组件,通过父组bus件共享变量进行通信。如下,子组件1给子组件2

function App(){
  const [name,setName] = useState('王新');
  const getChildMsg = (msg)=>{
    setName(msg);
  };
  return (
    <div className="App">
        <Child1 getChildMsg={getChildMsg}/>
        <Child2 name={name}/>
    </div>
  );
}
// 子组件1
function Child1(props){
  const senMsgToParent = ()=>{
    props.getChildMsg('丁少华')
  };
  return (
    <div>
      <button onClick={senMsgToParent}>子组件1给父组件发送通知</button>  
    </div>
  );
}
// 子组件2
function Child2(props){
  const senMsgToParent = ()=>{
    props.getChildMsg('你好,我是子组件1')
  };
  return (
    <div>{props.name}</div>
  );
}

React.Context

使用作用域之React.Context,这个学过java的人都知道,此对象是贯穿整个应用的。通过注入便监听 Context来达到redux同样的效果,而且不用引入第三方包。
React.Context 和 hooks之useReducer搭配。是目前官方推荐的用法,毕竟redux再好,不是react自家的东西,而且还需要额外的引包。何况useReducer也借鉴吸收了redux的优点,上手并非难事
demo点击查看

EventBus

即发布订阅者模式的方式,这里就不手写了,引用了一个facebook的库fbemitter

// 懒得举例了,使用这个通信就可以随意且不受约束,想怎么通信都可以,甚至跨组件

Redux

  • 其实 Redux 就是 Event 的思想,也是基于发布/订阅模式来实现的,里面提到的 action,store,reducer 分别是 EventHub 里的trigger 事件,全局数据,监听事件的回调代码
  • 区别是 Flux/Redux提供了更好的架构设计,其状态管理规范遵循react本身的自上而下的单向数据流的概念, 由此保证没有额外的循环引发大量的局部状态导致复杂度. 而EventBus只是提供了观察者模式的规范,只是单纯的事件通讯,没什么方向的限制.
  • 所以,React 一般会尝试避免用 EventBus 免得出现奇怪的局部状态.

使用redux也可以随意通信,不受约束。具体请看Redux Api
demo点击查看

4、路由-简介

React Router 是一个基于 React 之上的强大路由库,是完整的 React 路由解决方案,它可以让你向应用中快速地添加视图和数据流,同时保持页面与 URL 间的同步。它拥有简单的 API 与强大的功能例如代码缓冲加载、动态路由匹配、以及建立正确的位置过渡处理。查看官方文档

如何代码中使用

建立路由模块

import React from 'react';
import { BrowserRouter as Router,Route} from 'react-router-dom';
import index from '../pages/index';
import Frend from '../pages/frend';
import Frdtl from '../pages/frdtl';
import About from '../pages/about';
export default (
    <Router>
        <div>
            <Route  path="/index" component={index}></Route>
            <Route  path="/frend" component={Frend}></Route>
            <Route  path='/frdtl/:sid' component={Frdtl}/>
            <Route  path="/about" component={About}></Route>
            <Route  exact path="/"  component={index}></Route>
        </div>
    </Router>
);

注入全局

import React, { Component } from 'react';
import './App.css';
import { BrowserRouter } from 'react-router-dom';
import Router from './router';
class App extends Component {
  render() {
    return (
      <BrowserRouter>
        {Router}
      </BrowserRouter>
    );
  }
}
export default App;

如何使用

import React, { Component } from 'react';
import { Link } from "react-router-dom";
export default class Tabbar extends Component {
    constructor(porps) {
        super(porps);
        this.state = {
            tabbar: [
                { url: '/', title: '首页' },
                { url: '/frend', title: '动态' },
                { url: '/about', title: '我的' }
            ]
        };
    }
    render() {
        return (
            <div className='tabs'>
                {this.state.tabbar.map((item, index) =>
                    <div className={this.props.active == index ? 'tab tabActive' : 'tab'} key={item.title.toString()}>
                        <Link to={item.url}>{item.title}</Link>
                    </div>,
                )}
            </div>
        );
    }
}

效果预览

5、路由-传参

比如我从博客列表页 跳转到博客详情页,需要传一个bid

通过query

路由声明不需要额外配置

<Route path='/list/listDtl'  component={ListDtl}/>

浏览器url显示也干净
http://localhost:3000/list/listDtl
使用Link组件传递

<Link to={{pathname:'/list/listDtl',query:{bid:blog.bid}}}>
    {blog.title}
</Link>

使用js传递

const goBlogDtl = (bid)=>{
    // 跳转路由
    props.history.push({
      pathname:'/list/listDtl',
      query:{bid}
    })
  }

详情页面接受参数

console.log(props.location.query)

注意不要刷新,一刷新query对象可就整个没了

通过state

用法和quer一模一样,特性也一样

通过url传递-restfull风格(param)

这个无惧刷新,但是看起来挺丑的,用起来也需要额外配置

路由需要额外相关配置

<Route path='/list/listDtl/:bid' component={ListDtl}/>

浏览器url看起来是属于restFul风格
http://localhost:3000/list/listDtl/201901
使用link组件传递:传递过程也需要拼接,跟路由声明对应

<Link to={'/list/listDtl/'+blog.bid}>{blog.title}</Link>

使用js改变路由传递

props.history.push({
    pathname:`/list/listDtl/${bid}`,
})

接收参数

props.match.params

通过url传递-传统风格

一样的丑,但是也无惧刷新,用法跟原始html通过url传递参数一样

路由不需要额外声明

<Route path='/list/listDtl'  component={ListDtl}/>

浏览器url满满的时代感
http://localhost:3000/list/listDtl?bid=201901
传递参数

props.history.push({
   pathname:`/list/listDtl?bid=${bid}`,
})

获取参数

// 打印'?bid=201901' 不是对象,需要对其额外的解析
  console.log(props.location.search)

总结

query和state都是隐式传递,不要要额外配置路由,url上也不会显示,但是一刷新就没了
param和?XX=XX的形式传递 都是显式传递,url上都会显示,刷新也还在,唯一的区别是后者不需要配置路由

6、路由-嵌套

注意版本

从react-router4开始,嵌套路由已经不支如下的持嵌套式写法,需要留意:

<Route path='/about' component={About}>
    <IndexRoute component={Heart}/>
    <Route path='/photo' component={Photo}/>
</Route>

造成的重大影响是:react再也无法像vue那样,抽离出来一个独立模块,统一化路由管理了。路由分散在各个组件里。不过也有好处,比如看起来更加清晰易于阅读

看效果图,我要实现这么一个功能

简单一分析:
我需要3个平级路由,
其中第三个’关于‘还要嵌套两个子路由
于是使用脚手架搭建架构,并且新建文件如下

代码实现

定义平级路由
首先安装路由库react-router-dom,并修改入口文件index.js如下

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import MyHead from './components/myHead';
import News from './pages/news';
import Blogs from './pages/blogs';
import About from './pages/about';
import { BrowserRouter, Route } from "react-router-dom";
ReactDOM.render(
    <BrowserRouter >
        <MyHead/>
        <Route path='/' exact component={News}/>
        <Route path='/blogs' component={Blogs}/>
        <Route path='/about' component={About}/>
    </BrowserRouter>, 
document.getElementById('root'));

创建顶部主导航栏组件src/components/myHead.js

import React from 'react';
import { NavLink } from "react-router-dom";
function MyHead() {
  const navs = [
    {title:'首页',hrf:'/',exact:true},
    {title:'博客',hrf:'/blogs',exact:true},
    //因为嵌套路由子路由选中,主路由也得是选中状态,所以exact不能精准匹配
    {title:'关于',hrf:'/about',exact:false} 
  ]
  return (
    <div className="MyHead">
        {navs.map((item)=>(
          <NavLink key={item.hrf} className="navLink" activeClassName="navActive" to={item.hrf} exact={item.exact}>
            {item.title}
          </NavLink>
        ))}
    </div>
  );
}
export default MyHead;

创建首页src/pages/news.js

import React from 'react';
function News() {
  return (
    <div className='News'>
     我是首页
    </div>
  );
}
export default News;

创建博客页src/pages/blogs.js

import React from 'react';
function Blogs() {
  return (
    <div className="Blogs">
     我是Blogs
    </div>
  );
}
export default Blogs;

创建 关于我 页面 src/pages/about.js以及其声明子路由

import React from 'react';
import { Route,NavLink } from "react-router-dom";
import Heart from './aboutCmp/heart';
import Photo from './aboutCmp/photo';
function About() {
  return (
    <div className="About">
        <div className='aboutNav'>
            {/* exact控制选中高亮匹配规则,如果为true则匹配严禁 */}
            <NavLink exact to='/about' className="navLink" activeClassName="navActive" >心情</NavLink>
            <NavLink exact to='/about/photo' className="navLink" activeClassName="navActive" >照片</NavLink>
        </div>
        <div className='aboutCtx'>
            {/* 跟v3版本不同的是,默认父路由显示子组件模块是如下第一种配置写法,取代了IndexRoute方式
             默认子组件的路由要标识exact为true 否则点击其他子路由,次路由也不会消失 同时显现
             嵌套路由中,父路由不用声明exact属性,否则只能匹配到默认路由的那个组件,其他子组件均不显示 */}
            <Route path='/about' exact component={Heart}/>  {/* 默认子组件的路由配置 */}
            <Route path='/about/photo' component={Photo}/>          
        </div>
    </div>
  );
}
export default About;

创建about的子路由heart页面src/pages/aboutCmp/heart.js

import React from 'react';
function Heart() {
  return (
    <div className="Heart">
        心情好着呢
    </div>
  );
}
export default Heart;

创建about的子路由photo页面src/pages/aboutCmp/photo.js

import React from 'react';
function Photo() {
  return (
    <div className="Photo">
     就不给你看
    </div>
  );
}
export default Photo;

嵌套路由使用场景

比如后台管理或者大数据分析展示类型网站比较多用这种ui风格

7、高阶组件

什么是高阶组件

接收函数作为输入,或者输出另一个函数的一类函数,被称作高阶函数。对于高阶组件,它描述的便是接受React组件作为输入,输出一个新的React组件的组件。更通俗地描述为,高阶组件通过包裹被传入的React组件,经过一系列处理,最终返回一个相对增强的React组件,供其他组件调用。

横切关注点

使用 HOC 解决横切关注点问题
高阶组件是react应用中很重要的一部分,最大的特点就是重用组件逻辑。它并不是由React API定义出来的功能,而是由React的组合特性衍生出来的一种设计模式。如果你用过redux,那你就一定接触过高阶组件,因为react-redux中的connect就是一个高阶组件。

两种方式实现

方式1:属性代理

通过hoc包装wrappedComponent,在hoc可以获取到原组件的各种props,这个一会再讲。先看简单例子

import * as React from 'react';
import A from './components/a';
class App extends React.Component {
  public render() {
    return (<A/>);
  }
}
export default App;
import * as React from 'react'
//继承式高阶组件
const myHoc = (Cmp: any)=> {
    return class Myhoc extends React.Component{
        static displayName  = `Myhoc_${Cmp.displayName || Cmp.name}`;
        render(){
            return (
            <div className='Myhoc'>
                <div>{Myhoc.displayName}</div>
                <Cmp/>
            </div>
        )}
    }
};
export default myHoc;
import * as React from 'react';
import Myhoc from './myhoc';
class A extends React.Component{
    render() {
        return (<div>i am a</div>);
    }
};
export default Myhoc(A);

方式2:反向继承

反向继承II,跟属性代理的方式不同的是,通过去继承WrappedComponent,本来是一种嵌套的关系,结果II返回的组件却继承了WrappedComponent。通过继承WrappedComponent,除了一些静态方法,包括生命周期,state,各种function,我们都可以得到

import React from 'react';
import A from './components/a';
function App() {
  return (
      <>
        <A/>
      </>
  );
};
export default App;
import * as React from 'react';
//继承式高阶组件
const myHoc = (Cmp)=> {
    return class Myhoc extends Cmp{
        static displayName  = `Myhoc_${Cmp.displayName || Cmp.name}`;
        render(){
            return (
                <div className='Myhoc'>
                    <div>{Myhoc.displayName}</div>
                    {super.render()}
                </div>
            )   
        }
    }
};
export default myHoc;
import * as React from 'react';
import Myhoc from './myhoc';
class A extends React.Component {
    render() {
        return (<div>i am a</div>)
    }
};
export default Myhoc(A);

第三章:考点

react

1、组件都有哪些划分?
根据组件的定义方式:函数组件,类组件
根据组件内部是否维护state:无状态组件,有状态组件
根据组件的职责:展示型组件和容器型组件
根据用户在元素上交互引起应用state改变:受控类型和非受控类型
在普通组件外面包一层逻辑的组件:高阶组件,普通组件

2、react生命周期函数?
大致分为3个阶段,具体看第一章,第7节:

  • 初始化阶段
  • 运行中状态
  • 销毁阶段

3、react性能优化是哪个周期函数?
shouldComponentUpdate 这个方法用来判断是否需要调用render方法重新描绘dom。因为dom的描绘非常消耗性能,如果我们能在shouldComponentUpdate方法中能够写出更优化的dom diff算法,可以极大的提高性能。

4、为什么虚拟dom会提高性能?
参考知乎回答。虚拟dom相当于在js和真实dom中间加了一个缓存,利用dom diff算法避免了没有必要的dom操作,从而提高性能。
具体实现步骤如下:

  1. 用 JavaScript 对象结构表示 DOM 树的结构,然后用这个树构建一个真正的 DOM 树,插到文档当中 。
  2. 当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异 。
  3. 把2所记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新了。

5、什么是高阶组件?
高阶组件类似于高阶函数,即指接受React组件作为参数,输出一个新的组件的函数,在这个函数中,我们可以获取或修改传入组件的props与state。无入侵式扩充修改原组件,类似于横切关注点的aop,所以在一些特定情况下高阶组件可以让我们的代码看起来更优美(尤其使用注解),更具有复用性

6、调用 setState 之后发生了什么?
在代码中调用 setState 函数之后,React 会将传入的参数对象与组件当前的状态合并,然后触发调和过程。调和过程就是新旧virtual dom对比,然后更新真实dom。
当调用setState时,它并不会立即改变,而是会把要修改的状态放入一个任务队列,等到事件循环结束时,再合并指更新。
因此,setState有异步,合并更新更新两个特性。

7、setState是为什么是异布的?

  • 保证内部数据统一
  • setState异布更新状态使得并发更新组件成为可能。
  • 如果是同步的话,碰频调setStateReact会频繁渲染,性能和体验都很差,所以采用了异步更新的方式,将数次变动集中起来更新

参考简书

8、React 中 refs 的作用是什么?
Refs 是 React 提供给我们的
安全访问 DOM 元素(用在原生html元素上)
或者某个组件实例的句柄(用在自定义元素上)

9、React 中有三种构建组件的方式
React.createClass()、ES6 class 和无状态函数。

10、React 中 keys 的作用是什么?
Keys 是 React 用于追踪哪些列表中元素被修改、被添加或者被移除的辅助标识。
在开发过程中,我们需要保证某个元素的 key 在其同级元素中具有唯一性。在 React Diff 算法中 React 会借助元素的 Key 值来判断该元素是新近创建的还是被移动而来的元素,从而减少不必要的元素重渲染。此外,React 还需要借助 Key 值来判断元素与本地状态的关联关系,因此我们绝不可忽视转换函数中 Key 的重要性。

11、react diff 原理(常考,大厂必考)

  • 把树形结构按照层级分解,只比较同级元素。
  • 给列表结构的每个单元添加唯一的 key 属性,方便比较。
  • React 只会匹配相同 class 的 component
  • 合并操作,调用 component 的 setState 方法的时候, React 将其标记为 dirty.到每一个事件循环结束, React 检查所有标记 dirty 的 component 重新绘制.
  • 选择性子树渲染。开发人员可以重写 shouldComponentUpdate 提高 diff 的性能。

12、react的优势和不足

1. 实现对虚拟DOM的操作,使得它速度快,提高了Web性能。
2. 组件化,模块化。react里每一个模块都是一个组件,组件化开发,可维护性高。
3. 单向数据流,比较有序,有便于管理,它随着React视图库的开发而被Facebook概念化。
4. 跨浏览器兼容:虚拟DOM帮助我们解决了跨浏览器问题,它为我们提供了标准化的API,甚至在IE8中都是没问题的。
1. react中只是MVC模式的View部分,要依赖引入很多其他模块开发。(比如引入状态管理器:React扮演的是View的角色,Redux则是Controller,至于Model就是Redux Store中存储的State)
2. 当父组件进行重新渲染操作时,即使子组件的props或state没有做出任何改变,也会同样进行重新渲染。

13、React如何性能优化
1、定制shouldComponentUpdate函数,决定react组件什么时候能够不重新渲染的函数
2、遍历优化,给你的DOM遍历上加上唯一的key,注意尽量不要用index,因为如果你新DOM中删了某一个节点,它会重新排列index,那跟原来同层级一比就都会完全不一样,而重新渲染了

redux

1、redux工作流程?

首先,用户发出 Action。

store.dispatch(action);

然后,Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 Action。 Reducer 会返回新的 State 。

let nextState = todoApp(previousState, action);

State 一旦有变化,Store 就会调用监听函数。

store.subscribe(listener); // 设置监听回掉函数

listener可以通过store.getState()得到当前状态。如果使用的是 React,这时可以触发重新渲染 View。

function listerner() {
  let newState = store.getState();
  this.setState(newState);   
}

2、为何使用React-Redux?
避免手动管理store与react关系的麻烦,它已经绑定好了两者的关系。

3、自己写一个中间件?
它提供的是位于 action 被发起之后,到达 reducer 之前的扩展点,你可以利用 Redux middleware 来进行日志记录、创建崩溃报告、调用异步接口或者路由等等。
定义

const logger = store => next => action =>{
    console.log('prev state',store.getState())
    console.log('dispatch',action);
    let result = next(action);
    console.log('next state',store.getState());
    return result;
}

使用

const store = createStore(reducers,applyMiddleware(logger));

参考文档:redux docs简书博客

4、异步处理方案中间件?
所有的Action都是同步Action,即本地数据塞进Action中立即dispatch出去更新state。但现实中很多数据是需要从服务器端取的 比如常见ajax方式取数据,因此需要异步Action
异步Action是个plan object,不存在同步或者异步的概念。所谓的异步Action,本质上是一系列Action动作:
第一步:先dispatch出请求服务器数据的Action(通常此时state里会设计个loading或fetching的值,让页面呈现出loading状态)
第二步:服务器返回了数据(也可返回异常),将数据塞入Action里,再dispatch出这个Action去更新state。
redux-thunk VS redux-saga

posted @ 2023-01-12 11:06  丁少华  阅读(129)  评论(0编辑  收藏  举报