上海千语创想科技有限公司
 175-2108-6175
网站建设资讯详细

React框架搭建过程,具体步骤是什么?

日期:2022-11-21  作者:千语创想  浏览:6472

React框架搭建步骤:

1、使用create-react-app创建项目。

2、CSS reset。

3、配置SCSS。

4、配置styled-component。

5、使用React-Router。

6、引入SVG Icon。

1、使用creact-react-app创建项目:

先创建项目目录,把目录放拖进VSCode里面,在终端输入create-react-app . --template typescript,然后输入yarn start

如果不喜欢每次yarn start都自动打开浏览器的话,就在项目下新建一个文件,命名为.env,然后加上BROWSER=none

如果用 WebStorm,就在 .gitignore 添加 /.idea

如果用 VSCode,就在 .gitignore 添加 /.vscode

如果使用 Git status发现.idea或者.vscode有改动,则添加一下以下代码:

git rm -rf --cached .idea
git rm -rf --cached .vscode

然后提交代码,则创建项目完成了。

在创建好的项目当中,index.tsx文件里面有一个React.StrictMode,作用是检查代码哪里有用错的地方,可以删掉。

react项目创建好以后,是没有内置Router,Redux和SCSS的,需要的话,看文档自己去安装。

2、CSS Reset:样式重置,把所有默认样式全部去掉

normalize:让各个浏览器的样式都趋于相同。(让默认样式基本一样)。

在 index.css 添加 @import-normalize; 即可。但是默认样式一般会被覆盖掉的,所以用处不大。

3、配置SCSS:(node-sass比较难用,所以不建议使用,使用dart--sass代替)

终端输入:yarn add --dev node-sass@npm:dart-sass,然后把css文件后缀改为scss,就可以成功使用sass了。

react早就支持直接在SRC目录下直接引用了,所以为了让css@import引用更方便,直接绝对用就可以了。

而对于js的引用,首先在tsconfig.json里面compilerOptions低下加上"baseUrl": "src",这句话,然后要保证外面要有"include": ["src"]这一句话。然后js就可以直接引用src低下的文件了。

然后创建helper.scss文件,用来存放变量,函数等公用的东西。至此,css和js就配置完了。

使用css,可以在标签那里用calssnName写上标签名,然后导入css文件,就可以使用了。

在index.scss文件夹里面把全局样式设置好。

4、配置styled-component:

除了可以把css单独放在一个文件里面的方法,还可以使用CSS-in-JS的方案:styled-component。

在终端输入 yarn add styled-components以及yarn add --dev @types/styled-components(这是typescript的支持文件,一般支持文件都是加--dev的)

5、使用React-Router:

首先添加react router以及它的typescript支持文件:

终端运行:yarn add react-router-dom以及yarn add --dev @types/react-router-dom,然后直接看文档使用即可: React Router: Declarative Routing for React

v6做了升级,具体改动可以参考:react-router-dom v6 移除Redirect后的解决方案

需要配置默认路由"/"以及错误路由"*"

如果没有后台服务器,就只能使用Hash模式,如果有后台服务器,配置所有路径都到首页才能History模式。

配置Hash模式:

只要把Router的类型改为HashRouter就可以了。

6、引入SVG Icon(直接引入svg的话不能改颜色,所以建议使用Svg symbols的方式):这个可引入可不引入。

首先把需要使用的svg文件下载好,然后在终端输入yarn eject(注意,eject以后,就不会把webpack文件隐藏回去了),eject成功以后,记得提交一下代码。接下来在终端输入yarn add --dev svg-sprite-loader。成功以后,打开config/webpack.config.js,return-module-rules-oneOF,在这个数组的最上面,添加代码:

 {
 test: /.svg$/,
 use: [
            { loader: "svg-sprite-loader", options: {} },
            { loader: "svgo-loader", options: {} },
          ],
        }, 

然后终端添加“svgo-loader":yarn add --dev svgo-loader

然后重新运行yarn start,就可以使用了。

使用方法:(因为TreeShaking不适于require,适于Import,所以用import需要调用一下,svg才会显示,而rquire则不需要,所以使用require更方便。)

require("icons/money.svg");

 <svg fill="red" className="icon">
 <use xlinkHref="#money" />
 </svg>

然后就可以设置icon的样式了。

最后,require一个目录/一个文件夹,便于导入svg。

输入一以下代码:

let importAll = (requireContext: __WebpackModuleApi.RequireContext) =>
 requireContext.keys().forEach(requireContext);
try {
 importAll(require.context("icons", true, /.svg$/));
} catch (error) {
 console.log(error);
}

然后终端输入:yarn add--dev @types/webpack-env,消除__WebpackModuleApi警告。

然后在context后面输入svg路径,再关掉重启。就可以了。这时候,就可以自定义一个icon组件,去自由使用了。

假如一个应用里面,有许多页面重复,那可以写一个脚本去自动生成代码,方便,快捷。


这里贴一些公共类的代码:

1、index.scss:

@import-normalize;
@import "heplper.scss";
* {
 margin: 0;
 padding: 0;
}
* {
 box-sizing: border-box;
}
*::before {
 box-sizing: border-box;
}
*::after {
 box-sizing: border-box;
}
ul,
ol {
 list-style: none;
}
a {
 text-decoration: none;
 color: inherit;
}
body {
 font-family: $font-hei;
 font-size: 16px;
 line-height: 1.2;
}
.icon {
 width: 1em;
 height: 1em;
}

index.tsx:

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

这样子,index.scss定义的全局样式就可以在全局使用了。

2、helper.scss:

$font-hei: -apple-system, "Noto Sans", "Helvetica Neue", Helvetica,
 "Nimbus Sans L", Arial, "Liberation Sans", "PingFang SC", "Hiragino Sans GB",
 "Noto Sans CJK SC", "Source Han Sans SC", "Source Han Sans CN",
 "Microsoft YaHei", "Wenquanyi Micro Hei", "WenQuanYi Zen Hei", "ST Heiti",
  SimHei, "WenQuanYi Zen Hei Sharp", sans-serif;

3、icon.tsx:

import React from "react";
//require一个目录/文件夹
let importAll = (requireContext: __WebpackModuleApi.RequireContext) =>
 requireContext.keys().forEach(requireContext);
try {
 importAll(require.context("icons", true, /.svg$/));
} catch (error) {
 console.log(error);
}

type Props = {
 name: string;
};
const icon = (props: Props) => {
 return (
 <svg className="icon">
 <use xlinkHref={"#" + props.name} />
 </svg>
  );
};
export default icon;


细节点注意:

1、在Router里面,使用<NavLink to="" activeClassName="selected">,然后加上css,那么点击就会改变样式,适合做导航栏icon点击效果。

6.0版本官方已经不再支持activeClassName这种写法了,换成动态设置className即可:

<NavLink className={({ isActive })=>"list-group-item"+(isActive ?" mactive":"")} to="/about">               About 		</NavLink><NavLink className={({ isActive })=>"list-group-item"+(isActive ?" mactive":"")} to="/home">               Home          </NavLink>

也可以不加className,6.0版本使用NavLink 默认点击就会有class="active"这个标签。

2、在处理导航栏点击改变样式的时候,有些SVG图片自带颜色,改变不了,这时候可以使用svgo来处理。在SVGO Loader里面设置如下:

                {
                  loader: "svgo-loader",
                  options: {
                    plugins: [
                      {
                        name: "removeAttrs",
                        params: {
                          attrs: "(fill|stroke)",
                        },
                      },
                    ],
                  },
                },

3、styled-component可以给自定义的组件再做一层封装,如下:

import Layout from '../components/Layout';
const MyLayout = styled(Layout)`
  display:flex;
  flex-direction: column;
`
function Money() {
  return (
    <MyLayout>
      <TagsSection>
        <ol>
          <li>衣</li>
          <li>食</li>
          <li>住</li>
          <li>行</li>
        </ol>
        <button>新增标签</button>
      </TagsSection>
    </MyLayout>
  );
}

export default Money;

4、在react里面的函数传值,可以使用匿名函数,不能出现onClick={onToggleTag(tag)}这样的代码,因为这代表的是马上调用行数,函数,然后把得到的值给onClick,这是错误的,我们需要的是点击的时候才调用函数,而不是马上执行函数,所以应该这样调用:onClick={()=>{onToggleTag(tag)}}。完整代码如下:

const onAddTag = () => {
    console.log("object");
    const tagName = window.prompt("新标签的名称");
    if (tagName !== null) {
      setTags((t) => [...tags, tagName]);
    }
  };
  const onToggleTag = (tag: string) => {
    console.log(tag);
  };
  return (
    <Wrapper>
      <ol>
        {tags.map((tag) => (
          <li
            key={tag}
            onClick={() => {
              onToggleTag(tag);
            }}
          >
            {tag}
          </li>
        ))}
      </ol>
      <button onClick={onAddTag}>新增标签</button>
    </Wrapper>

5、判断选中的元素是否在数组里面,是的话删除,不是的话添加的方法:

const onToggleTag = (tag: string) => {
 const index = selectedTags.indexOf(tag);
 console.log("index:", index);
 if (index >= 0) {
 setSelectedTags(selectedTags.filter((t) => t !== tag));
 //如果tag已被选中,就复制所有没有被选中的tag,作为新的selectedTag
    } else {
 setSelectedTags((t) => [...selectedTags, tag]);
    }
  };

6、在index.tsx里面用到的严格模式,有时候会引起一些问题(目前暂时遇到log问题),可以删除严格模式。

严格模式:

ReactDOM.render(
 <React.StrictMode>
 <App />
 </React.StrictMode>,
 document.getElementById('root')
);

非严格模式:

ReactDOM.render(<App />, document.getElementById("root"));

7、input的受控模式:

const NoteSection: React.FC = () => {
 const [note, setNote] = useState<string>("");
 console.log(note);
 return (
 <Wrapper>
 <label>
 <span>备注</span>
 <input
 type="text"
 placeholder="在这里添加备注"
 value={note}
 onChange={(e) => setNote((t) => e.target.value)}
 />
 </label>
 </Wrapper>
  );
};

Input的非受控模式:(不管中间过程,只要鼠标移除,就获取结果)

const NoteSection: React.FC = () => {
 const [note, setNote] = useState<string>("");
 const refInput = useRef<HTMLInputElement>(null);
 const onBlur = () => {
 if (refInput.current !== null) {
 setNote(refInput.current.value);
    }
  };
 return (
 <Wrapper>
 <label>
 <span>备注</span>
 <input
 type="text"
 placeholder="在这里添加备注"
 ref={refInput}
 defaultValue={note}
 onBlur={onBlur}
 />
 </label>
 </Wrapper>
  );
};

注意:

React onChange和HTML onChange 是不一样的。

React onChange会在输入一个字的时候触发,

HTML onChange在鼠标移走的时候触发,早于onBlur。

8、当出现Element implicitly has an 'any' type这个错误的时候,说明类型使用有误,这时候可以把类型收缩一下范围:

const [categoryList] = useState<("-" | "+")[]>(["-", "+"]);

9、哈希类型的使用案例:

const CategorySection: React.FC = () => {
 const [categoryList] = useState<("-" | "+")[]>(["-", "+"]);
 const [category, setCategory] = useState("-"); //+表示收入 -表示支出
 const categoryMap = { "-": "支出", "+": "收入" };
 return (
 <Wrapper>
 <ul>
 {categoryList.map((e) => (
 <li
 key={e}
 className={category === e ? "selected" : ""}
 onClick={() => {
 setCategory(e);
            }}
 >
 {categoryMap[e]}
 </li>
        ))}
 </ul>
 </Wrapper>
  );
};
export { CategorySection };

另外一个变式:

 const categoryMap = { "-": "支出", "+": "收入" };
 type Keys = keyof typeof categoryMap;//通过这个方法,获取到哈希对象里面的值,然后作为类型。
 const [categoryList] = useState<Keys[]>(["-", "+"]);
 const [category, setCategory] = useState("-"); //+表示收入 -表示支出

10、内部数据读写如下:

 const [selectedTags, setSelectedTags] = useState<string[]>([]);//直接使用useState进行内部数据读和写。

接收外部数据,然后进行读写操作,组件可以读外部传进来的数据,但是要改变数据的话,则需要通过通知外部父组件修改才可以,不能在内部把外部数据改了。

读:

父组件: 
const [selected, setSelected] = useState({
 tags: [] as string[],
 note: "",
 category: "-" as Category,
 amount: 0,
  });
...
 <TagsSection selected={selected.tags} />

子组件:

type Props = { selected: string[] };
const TagsSection: React.FC<Props> = (props) => {
  const selectedTags = props.selected
  const getClass = (tag: string) =>
    selectedTags.indexOf(tag) >= 0 ? "selected" : "";//通过props读取外部数据
  return (
    <Wrapper>
     ...
    </Wrapper>
  );
};

写:

父组件:

<TagsSection
        selected={selected.tags}
        onChange={(tags) => setSelected({ ...selected, tags: tags })}
      />

子组件:
type Props = { selected: string[]; onChange: (selected: string[]) => void };//设置props的类型
...
 props.onChange([...selectedTags, tag]);//通知外部父组件修改数据

11、在typescript设置类型的时候,如果要表示我的对象是某个对象一部分的话,可以使用Partial<>来表示

 const [selected, setSelected] = useState({
 tags: [] as string[],
 note: "",
 category: "-" as Category,
 amount: 0,
  });
 type Selected = typeof selected;//通过typeof获取一个值的类型
 const onChange = (obj: Partial<Selected>) => {//通过Partial<Selected>,得到的类型是传入的类型的部分类型。
 setSelected({
      ...selected,
      ...obj,
    });
  };

12、注意,自定义hook都是要用use开头。

一个自定义hook的例子:

import React, { useState } from "react";

const useTags = () => {
 const [tags, setTags] = useState<string[]>(["衣", "食", "住", "行"]);
 return { tags, setTags };
};

export { useTags };


以上这种抽离出useState,并把读写接口放到外面去,这种操作就叫做自定义hook。

13、router可以使用精确匹配exact,这样子就不用考虑路由的顺序问题,但是在6.0版本里面,因为优化了,所以不用精确匹配,也不会因路由的顺序问题产生跳转失败的情况。另外,需要匹配子路由,可以使用一下写法:

 <Route path="/tags/:tag" element={<Tag />} />//匹配/tags/:*开头的任意路由。


在子页面获取路由的参数:

路由:
 <Route path="/tags/:id" element={<Tag />} />
页面:
import React from "react";
import { useParams } from "react-router-dom";
import { useTags } from "useTags";
type Params = { id: string };
const Tag: React.FC = () => {
 const { tags } = useTags();
 let { id } = useParams<Params>();//获取路由参数
 console.log("id:", id);
 const tag = tags.filter((tag) => tag.id === parseInt(id!))[0];
 return <div>{tag.name}</div>;
};

export { Tag };

14、自定义组件类型继承父类属性的写法:

type Props = {
 label: string;
} & React.InputHTMLAttributes<HTMLInputElement>;
const Input: React.FC<Props> = (props) => {
 const { label, children, ...rest } = props;
 return (
 <Label>
 <span>{props.label}</span>
 <input {...rest} />
 </Label>
  );
};


15、如果需要合并className,可以添加classname库,终端输入:

yarn add classnames

yarn add --dev @types/classnames

然后代码如下:

<svg className={cs("icon", className)} {...rest}>//会自动合并'icon'和className
 {props.name && <use xlinkHref={"#" + props.name} />}
 </svg>


16、后退操作:

window.history.back();//当我们采用哈希模式的时候,后退是没有新的页面刷新的,只是页面的状态切换而已。

注意 window.history.back();有个Bug,就是当用户复制地址在浏览器打开的话,再点击后退的话,会回到浏览器那里,而不是应用的上一页。


17、如果要实现某变量更新以后调用函数,除第一次以外,可以用如下代码:(自定义hook)

//
import { useEffect, useRef } from "react";

const useUpdate = (fn: () => void, deps: any[]) => {
 const count = useRef(0);
 useEffect(() => {
 count.current += 1;
  });
 useEffect(() => {
 if (count.current > 1) {
 fn();
    }
  }, deps);
};

export { useUpdate };

使用:
import { useUpdate } from "hooks/useUpdate";
 useEffect(() => {
    setTags(JSON.parse(window.localStorage.getItem("tags") || "[]"));
  }, []);
  useUpdate(() => {
    window.localStorage.setItem("tags", JSON.stringify(tags));
  }, [tags]);

18、创建两个相似的类型:

type RecordItem = {
 tagIds: number[];
 note: string;
 category: "+" | "-";
 amount: number;
 createAt: string; //ISO 8601
};

// type newRecordItem = {
//   tagIds: number[];
//   note: string;
//   category: "+" | "-";
//   amount: number;
// };

type newRecordItem = Omit<RecordItem, "createAt">;//Omit的意思是,作用于函数,忽略RecordItem里面的createAt属性。如果有两项,可以使用|来分割:type newRecordItem = Omit<RecordItem, "createAt"|"uodatedAt">;



19、给组件的属性添加默认值:

type Props = {
 className: string;
 scrollTop?: number;
};
Layout.defaultProps = {
 scrollTop: 0,
};//给组件属性添加默认值
const Layout: React.FC<Props> = (props) => {
 const mainRef = useRef<HTMLDivElement>(null);
 useEffect(() => {
 if (!mainRef.current) {
 return;
    }
 mainRef.current.scrollTop = props.scrollTop!;//因为有默认呢只,所以不可能为空的,所以加!。
  }, [props.scrollTop]);
 return (
 <Wrapper>
 <Main ref={mainRef} className={props.className}>
 {props.children}
 </Main>
 <Nav />
 </Wrapper>
  );
};
export default Layout;

20、使用setTimeout的时候,后面秒数为0不是说0秒就执行,而是尽快。

 useEffect(() => {
 setTimeout(() => {
 if (!mainRef.current) {
 return;
      }
 mainRef.current.scrollTop = props.scrollTop!;
    }, 0);
  }, [props.scrollTop]);


21、自动部署:

在scripts文件下,添加脚本deploy.sh,然后编辑即可。


来千语创想移动应用开发平台学习更多APP开发知识:app开发app制作app开发源码下载app开发框架app制作模板等免费获取。



转载请注明来自:https://www.qianyuthink.com/news/7439.html

填写您的项目需求给我们

或者直接拨打 7×12小时一对一咨询电话

175 2108 6175

请填写需求信息,我们会在10分钟内与您取得联系

请认真填写需求信息,我们会在10分钟内与您取得联系

×
客服二维码
咨询技术总监
175-2108-6175
客服二维码
技术总监微信
客服二维码