使用React Hooks 16版本开发项目

前言: 本文仅展示部分基础代码,不涉及原理。

一、 基本目录树

□ public (公共资源文件)
□ nav
□ sprite
- index.html
□ src (源代码)
□ common (公共模块)
□ components (组件/页面)
□ routes (路由文件)
□ style (样式)
□ utils (公共方法)
- App.js
- App.scss
- index.js (入口文件)

  • package.json
  • README.md

二、 入口文件示例

1. index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App />, document.getElementById('pageWrapper'));

2. App.js

import React from 'react';
import './App.scss';
import { BrowserRouter } from 'react-router-dom';
import { Home } from './routes/Home';

function App() {
    return (
        <BrowserRouter>
            <Home />
        </BrowserRouter>
    );
}

export default App;

三、 路由文件示例

1. routes/Home.js

// 用户登录后的主界面入口模块,界面结构为:左侧菜单栏+顶部导航条+内容区域
function AppContainer() {
  // 通常使用现有的UI组件 配合 <Swtich>和<Route>组件
  // 例
    return (
    <Layout>
      <!-- 顶部导航 -->
      <Header>
        <NavMenu/>  
      </Header>
      <Body>
        <!-- 左侧菜单 -->
        <Sider>
          <Menu>
            <Link to="/pathName">menuName</Link>
            ...
          </Menu>
        </Sider>
        <!-- 左侧菜单路由配置 -->
        <Switch>
          <!-- 页面 -->
          <Route path="/pathName" component={componentName}/>
          ... ...
          <!-- 内部错误页 -->
          <Route component={ErrorPage} /> 
        </Switch>
      </Body>
    </Layout>
  );
}

export default function Home() {
  return (
     <!-- 主路由配置,包括登录界面和登录后跳转的主界面 -->
    <Switch>
      <Redirect from="/" exact to="/home"></Redirect>
      <Route path="/login" component={Login} />
      <Route path="/home" component={AppContainer} />
      <!-- 系统错误页 -->
      <Route component={ErrorPage} />
    </Switch>
  );
}

2. 路由对应的 组件/页面

  • components/Example/Index.js
import React from 'react';

export default function Example() {
    return (
        <div>我是组件内容</div>
    )
};

四、 axios请求示例

1. lib/axios.js 封装axios方法

import Axios from 'axios';
import { request } from './request';

// axios 中间件注册  文档:https://github.com/axios/axios
const axiosInstance = Axios.create();

// 请求拦截示例
axiosInstance.interceptors.request.use(
    (config) => {
        let { url, data } = config;
        const headers = Object.assign({}, config.headers);
        // 统一请求网关接口
        url = "/gateway";
        // 请求头添加公共参数
        Object.assign(headers, {
        "X-Common-Action": ActionName,
        "X-Common-Service": ServiceName,
        ... ...
        });
        data = JSON.stringify(data);

        Object.assign(config, {
          url,
          headers,
        });
        return config;
    }, function (error) {
        return Promise.reject(error)
    }
)

// 响应拦截示例
axiosInstance.interceptors.response.use(
    res => {
        const { status, data } = res;
        
        if (status === 401) {
            window.location.href = '/login';
        }
        return res;
    }, error => {
        return Promise.reject(error);
    }
);

// 请求体添加公共参数
const mergeParams = (params) => {
  const commonParams = {
    Version: params.Version,
    Action: params.url,
    Service: params.name,
  };
  const proxyObj = {
    ...params.data,
  };
  return Object.assign(commonParams, proxyObj);
};

const axios = (config, params) => {
    const requestConfig = request(config);
    const { method, url, version } = requestConfig;
    return axios[method](url, mergeParams({ data: params, ...config }), {
      Version: version,
    });
};

// get请求 参数格式转换 (转换为 ?key=value&key=value 字符串)
function UrlDataFormat(param) {
  const keys = Object.keys(param);
  let requestBody = "";
  let requestobj = "";
  let requestend = "";
  let lastrequest = "";
  for (let i = 0; i < keys.length; i++) {
    if (typeof param[keys[i]] === "object") {
      const dd = param[keys[i]];
      const keyitems = Object.keys(dd);
      for (let k = 0; k < keyitems.length; k++) {
        if (typeof dd[keyitems[k]] === "object") {
          const keyon = Object.keys(dd[keyitems[k]]);
          for (let j = 0; j < keyon.length; j++) {
            if (typeof dd[keyitems[k]][keyon[j]] === "object") {
              const lastone = Object.keys(dd[keyitems[k]][keyon[j]]);
              for (let f = 0; f < lastone.length; f++) {
                lastrequest += `${keys[i]}.${keyitems[k]}.${keyon[j]}.${f}=${
                  dd[keyitems[k]][keyon[j]][f]
                }&`;
              }
            } else {
              requestend += `${keys[i]}.${keyitems[k]}.${keyon[j]}=${
                dd[keyitems[k]][keyon[j]]
              }&`;
            }
          }
        } else {
          requestobj += `${keys[i]}.${keyitems[k]}=${dd[keyitems[k]]}&`;
        }
      }
    } else {
      requestBody += `${keys[i]}=${param[keys[i]]}&`;
    }
  }
  return `?${requestBody}${requestobj}${requestend}${lastrequest}`;
}

axios.get = (url, params, config) =>
  axiosInstance({
    method: "get",
    url:
      url +
      UrlDataFormat({
        ...params,
      }),
    ...config,
  })
    .then((res) => {
      return Promise.resolve(res.data.Response);
    })
    .catch((err) => {
      return Promise.reject(err);
    });

axios.post = (url, params, config) => {
  return axiosInstance({
    method: "post",
    url,
    data: {
      ...params,
    },
    ...config,
  })
    .then((res) => {
      return Promise.resolve(res.data.Response);
    })
    .catch((err) => {
      return Promise.reject(err);
    });
};

export default axios;

2. lib/request.js (在axios.js文件里有调用request方法)

import apiName from './apiName';

export function request(serviceName) {
    const config = apiName[serviceName];
    return {
        url: `/${config.url}`,
        method: config.method,
        version: config.version,
    }
}

3. lib/apiName.js 配置接口列表

export default {
  Login: {
    method: "post",
    url: "/Login",
    version: "0.0.1",
  },
  ...
}

4. store/appApi.js 导出接口调用方法

import axios from "../lib/axios";

export function doLogin(data) {
  return axios({ name: "ServerName", url: "Login" }, data)
    .then((res) => Promise.resolve(res))
    .catch((err) => Promise.reject(err));
}

5. src/Login/Login.js 发起请求

import { doLogin } from '../../store/appApi';

doLogin(data).then(result => {
    if (!result.Response.Error) {
        history.push('/home'); // 登录成功,跳转到主界面
    }
})

五、 context状态管理

1. context/appContext.js 定义context

import React from "react";

export const AppContext = React.createContext({
  LoginInfo: {},
  setLoginInfo: () => {},
});

2. src/Login/Login.js 使用

import React, { useContext } from "react";
import { AppContext } from "../../context/appContext";

const appContext = useContext(AppContext);

// 更新context状态
appContext.setLoginInfo(Result);

// 获取context状态
console.log(appContext.LoginInfo);

六、 props 父子组件数据传递

1. parent.js 父组件

import React, { useState } from 'react';
import Child from './child';  // 引入子组件

function Parent(){
    const [state, setState] = useState(0);  // 定义要传递的数据
    
    // 定义要传递的方法
    function parentFn(){
        // 做某事...
    }

    return (
        <!-- 使用子组件,绑定props -->
        <Child state={state} setState={setState} parentFn={parentFn}></Child>
    );
}

2. child.js 子组件

import React, { useEffect } from 'react';

function Child(props){
    
    useEffect(() => {
        console.log(props.state);  // 访问父组件的state

        props.setState(1); // 修改父组件的state
    }, []);

    return (
        <button onClick={props.parentFn}>点击我触发父组件的parentFn方法</button>
    );
}

七、 常用 Hooks 新特性

1. useState

import React, { useState } from 'react';

function Example() {
    // ① 定义
    const [useName, setUseName] = useState("");
    const [age, setAge] = useState(0);
    const [list, setList] = useState([]);

    // ② 修改
    setUseName("小明");
    setAge(18);
    setList(['吃饭','睡觉']);

    return (
      <div>
        <!-- ③ 使用 -->
        <p>姓名: {useName}</p>
        <p>年龄: {age}</p>
        <p>爱好: </p>
        {list.map((item, index) => {
            return (
                <p key={index}>{item}</p>
            );
          })}
      </div>
    );
}

2. useEffect

import React, { useEffect } from "react";

function Example() {
    const [data, setData] = useState([]);
    const [params, setParams] = useState({});

    useEffect(() => {
        setData([]);
    }, []); // 参数[] 第一次渲染后,仅调用一次。常用于动态请求数据渲染页面。

    useEffect(() => {
        setData([]);
    }, [params]); // params参数每次改变时都触发调用。
}

3. useRef

import React, { useRef } from 'react';

function Example() {
    const divEle = useRef(null);  // 定义

    useEffect(() => {
        console.log(divEle.current);  // 访问
    }, []);

    return (
        <div ref={divEle}></div>  <!-- 绑定 -->
    );
}

4. useRouter

① 页面跳转

import React from 'react';
import useRouter from 'use-react-router';

function Example() {
    const { history } = useRouter();

    function goHome(){
        history.push('/home'); // 跳转到首页

        history.push('/home',{ Id: 1 }); // 跳转到首页,并传参数
    }

    return (
        <button onClick={goHome}>回到首页</button>
    );
}

② 获取传参

import React from 'react';
import useRouter from 'use-react-router';

function Example() {
    const { history } = useRouter();

    useEffect(() => {
        // 获得传递过来的参数
        console.log(history.location.state);
        console.log(history.location.state.Id);
    }, [])

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

推荐阅读更多精彩内容