React项目开发基本使用
React项目开发前需要安装node环境
- 搜索引擎搜索node.js进入官网
- 安装最新稳定版即可(node集成了npm,npm是node.js的包管理工具)
- 安装完成后在终端中输入
node -v
,npm -v
能输出版本信息则可正常使用(若提示命令找不到,自行搜索配置全局PATH)
搭建React项目推荐使用官方出品的脚手架create-react-app(俗称CRA)
(CRA无需安装或配置 Webpack 或 Babel 等工具。 它们是预先配置好并且隐藏的,因此你可以专注于代码,需要自定义及修改可以使用eject命令暴漏webpack配置文件)
全局安装CRA便于使用,执行npm i -g create-react-app
create-react-app [项目名]
执行后会在当前目录创建项目
cd [项目名]
进入项目根目录
npm run start
执行命令即可启动项目
CRA文档传送门
关于npm的用法不过多介绍,自行搜索相关文档。
这是一个可供在线开发的沙箱环境:codesandbox.io/s
JSX介绍
React应用通常使用JSX语法编写程序(不是必须使用JSX),JSX是一种 JavaScript 语法扩展,有点类似于 HTML/XML。
根据官网文档介绍,React DOM 在渲染所有输入内容之前,默认会进行转义。它可以确保在你的应用中,永远不会注入那些并非自己明确编写的内容。所有的内容在渲染之前都被转换成了字符串。这样可以有效地防止 XSS(cross-site-scripting, 跨站脚本)攻击,所以可以放心食用。关于XSS可以看下面的例子:
假设我们有一个电商系统的评论功能。
用户 A 提交评论[物流很快!]到服务器,然后用户 B 来访问网站,那么B就看到了 A 的评论[物流很快!]。
用户 C 提交评论[<script>console.log(document.cookie)</script>
],然后用户 B 来访问网站,这段脚本在 B 的浏览器直接执行,用户 C 的脚本就可以任意操作 B 的 cookie,有了 cookie,恶意用户 C 就可以伪造 B 的登录信息,由此所产生的安全负面影响不言而喻。
这是一个JSX示例
const ReactNode = ()=> ( <div className="my-class" //className={`my-class`}也可以使用JavaScript表达式 style={{ color: `red`,marginLeft: 8 }} //内联样式使用对象写法,第一个{}标注此处为JavaScript表达式,第二个括号为对象 onClick={(event) => { console.log(event.currentTarget.tagName); }} > {window.innerHeight} {/*这是注释...*/} </div> )
上面基础示例这种不是字符串,也不是HTML的写法就是JSX语法。
书写JSX中需要注意的地方:
- React组件首字母必须大写,小写字母开头的会被认为HTML内置组件,使用小写的React组件名会报错。
- JSX中用
{}
标识表示此处{}
中的代码为JavsScript表达式。 - 属性名遵循驼峰命名规范。
- 使用className与htmlFor代替class和for。
- 组件与组件之间是可以嵌套的。
- 在JSX语法中只能使用求值表达式,不能使用语句,所以不能使用
if...else...
,for
。 - 可以使用三元表达式
? :
,<span className={window?`trueClassName`:`falseClassName`}>a标签</span>
。 - ReactDOM.render时不用再写App(),App是函数组件,组件当标签用即可(
<App/>
),会自动调用App函数。 - 可以直接在{}中调用函数。
const GetComponent = (props) => { if (props) { return <div>你传入了props</div>; } else { return <div>没有props</div>; } }; //jsx语法 <> {GetComponent()} </> //如果书写<GetComponent/>,React会对函数进行处理,props会被赋值为{},在示例中,props则为true返回<div>你传入了props</div>
- 使用短路运算符 && ||
let visibled = false {visibled && <div>欢迎&&光临</div>} {!visibled || <div>欢迎||光临</div>}
- 只能有一个顶层标签,如果不想输出实际的标签,可以使用空标签
<React.Fragment></React.Fragment>
或缩写<></>
作为最外层元素包裹。 - 布尔类型、Null 以及 Undefined 将会忽略。false, null, undefined, true 是合法的子元素。但它们并不会被渲染
<div>{null}</div>
(UI呈现为空白)。 - JSX是可选的(但一般不会直接写React语法,代码可读性及编写效率远不及JSX)
<a href="https://facebook.github.io/react/">Hello!</a>
以下为对应不使用JSX写法:
React.createElement('a', {href: 'https://facebook.github.io/react/'}, 'Hello!')
- 内联样式接受的JavaScript对象值若为数字,则会自动添加默认单位(px)。如果要使用em或者是rem其他单位,则需要使用字符串。
{top:2,left:`${2}em`,right:'10rem',bottom:"11px"}
- dangerouslySetInnerHTML 是 React 为浏览器 DOM 提供 innerHTML 的替换方案。通常来讲,使用代码直接设置 HTML 存在风险,因为很容易无意中使用户暴露于跨站脚本(XSS)的攻击。因此,你可以直接在 React 中设置 HTML,但当你想设置 dangerouslySetInnerHTML 时,需要向其传递包含 key 为 __html 的对象,以此来警示你。例如:
function MyComponent() { return <div dangerouslySetInnerHTML={{__html: '<p>hi</p>'}} /> }
- 在JSX中只有属性名称但没有填写具体值的会被设为true,例如以下第一个 input 标签 disabled 虽然没设值,但结果和下面的 input 为相同:
<input type="button" disabled />; <input type="button" disabled={true} />
- 自定义属性。
若是希望使用自定义属性,可以使用 data-
。
<MyComponent data-attr="test" />
- 展开语法。在 ES6 中使用
...
是迭代对象的意思,可以把所有对象对应的值迭代出来设定属性,但要注意后面设定的属性会盖掉前面相同属性。
var props = { style: {"width":"20px"}, className: "main", value: "yo", } //value在后,则会覆盖掉props对象的value属性 <div {...props} value="str" />
- JSX不能直接渲染对象,否则会报错。确实想输出对象可以通过
JSON.stringify()
转为字符串输出。 - JSX允许在模板中插入数组,数组会自动展开所有成员,前提是数组项不能为普通JavaScript对象,但可以是React元素。
(上面只列出了我个人日常开发注意的点,可在搜索引擎搜索React JSX查看更多写法)
React元素
JSX写法最终都会通过babel被编译为React.createElement形式执行(参考注意事项13),这也是React组件中即使代码中没有显式调用过React,但却需要引入import React from 'react'
的原因。React.createElement的返回值ReactElement可以代表一个div,但ReactElement并不是真正的div(DOM对象),所以一般称ReactElement为虚拟DOM元素。返回ReactElement的函数,也可以代表一个div,这个函数可以多次执行,每次得到最新的虚拟div,React会对比两个虚拟DOM找出不同,局部更新视图,找不同的算法叫做DOM Diff算法。
函数组件与class组件,此处主要介绍class组件
无状态组件可以用纯函数式组件(React16.8新出的Hook API可模拟class组件的特性,实现管理自身状态及生命周期),此处不深入讲解。
//函数组件就是一个纯函数 function MyFnComponent(props){ return (<div>{props.name}</div>) } //需要注意的是,上述的函数组件,如果用标签调用并且不传入props,props为空对象;用函数的用法不传入props则为undefined。
有状态组件用 ES6的class写法
下面是一个基本的class组件写法
//class实际是原型的语法糖,Prototype,__proto__,继承等JavaScript知识不多赘述 //class组件需要继承React.Component //背关键字(extends,constructor,super) export default class MyClassComponent extends React.Component { constructor(props) { super(props) //这一步不可少,调用父类的构造函数 this.state = { data: [1, 2, 3, 4] //初始化自身可管理的状态 } } //xxFn = ()=> {}这种写法为es6新语法,会自动绑定this addOne = () => { let { data } = this.state this.setState({ data: [...data, data.length + 1] }) //解构赋值写法 // this.setState({ data: data.concat([data.length+1]) }) //用数组提供的Api concat同等效果 } render() { let { data } = this.state; return ( <> <div>{data}</div> {/* 数组可直接渲染合法项 */} <button onClick={this.addOne}>点我数组加一项</button> </> ) } }
Props外部数据
当 React 元素为用户自定义组件时,它会将 JSX 所接收的属性(attributes)以及子组件(children)转换为单个对象传递给组件,这个对象被称之为 “props”。
初始化props,外部数据由父组件提供
class Child extends React.Component { constructor(props) { super(props) //将props传给super后,this.props就是外部数据对象的地址 } render(){} }
读取props
const onClick = ()=>{ console.log('u click me') } <Child name='JackMa' onClick={onClick}> <div>i am children</div> </Child> //父组件 //---------------- class Child extends React.Component { constructor(props) { super(props); } render(){ return ( <div onClick={this.props.onClick}> //点击事件为打印u click me {this.props.name} //输出JackMa <div> {this.props.children} //<div>i am children</div> //this.props.children包含组件的开始标签和结束标签之间的内容 </div> </div> ) } }
不要尝试修改props,保持props的只读性,外部数据就要由外部更新,确实需要修改则可以把修改props的方法当作props传进来供子组件使用,本质还是调用父组件的修改数据方法。
state && setState 内部数据
初始化state,外部数据由父组件提供
class Child extends React.Component { constructor(props) { super(props) this.state = { userInfo : { name : 'JackMa', age : 18, gold :9999, } } } render(){} } //使用es6写法 class Child extends React.Component { state = { //与写在constructor中效果一致,state最终都会绑在实例上 userInfo : { name : 'JackMa', age : 18, gold :9999, } } render(){} }
写state,使用setState
setState有两种写法,fn为可选的回调函数,它将在 setState 完成合并并重新渲染组件后执行,多次连续调用setState最终会被合并成一次
setState({key:value},fn)
this.setState({name:'Tony'},()=>{console.log('i execute')})
setState((state,props)=>newState,fn)
,updater 函数接收的state和porps参数都为最新
this.setState( (state, props) => {name:'Tony'}, // () => { console.log("i execute"); } )
setState是异步的,执行后读取this.state并不是最新的state,会在当前代码运行完后,再去更新this.state从而触发UI更新,使用 componentDidUpdate 或者 setState 的回调函数来读取更新后的state,这两种方式都可以保证在应用更新后触发。
setState会自动将新state和旧state进行一级合并
class组件的生命周期
constructor()
作用:
- 初始化props
- 初始化state,但不能调用this.setState
- 绑定this(band)
class Child extends React.Component { constructor(props) { super(props) this.state = { data : [1,2,3] } this.testFn = this.testFn.bind(this) } testFn(){ console.log(this) } render(){ return ( <button onClick={this.testFn}>click</dutton> ) } }
子类没有显式声明constructor方法时,执行过程中会自动补上constructor
class Child extends Father { } // 等同于 class Child extends Father { constructor(...args) { super(...args); } }
记住一个潜规则,constructor写了就必须写super(),否则直接报错。
shouldComponentUpdate()
作用:
- 组件每次执行redner前都会执行
- 函数返回false表示阻止UI更新
- 函数返回true表示允许UI更新
- 手动判断组件是否要进行更新,根据实际业务逻辑灵活的设置返回值,避免不必要的UI更新
- React.PureComponent内置了此功能,可以替代React.Component
//可以将newState和this.state进行对比,如果有不同则返回true进行更新 shouldComponentUpdate(newProps,newState){ //函数接受两个值,新的props和新的state if(newState.key === this.state.key){ return false }else{ return true } }
render()
作用:渲染视图
render(){ return (<div>halo,world!</div>) }
componentDidMount()
作用:
- 会在组件挂载后(插入 DOM 树中),执行代码,这些代码依赖DOM
- 可以获取DOM元素的信息
- 官方推荐在此处发起Ajax请求
- 只有首次渲染组件时会执行此钩子
componentDidMount(){ console.log('我只会执行1次') //以下为伪代码 handelDom() //操作DOM元素 ajax() //发起ajax请求 }
componentDidUpdate()
作用:
- 在视图更新后执行代码
- 组件首次挂载时不会执行此钩子
- 此处也可以发起Ajax请求
- 不要直接调用this.setState,否则会进入死循环,必须包裹在条件判断语句中。
- 如果 shouldComponentUpdate() 返回值为 false,则不会调用 componentDidUpdate()。
可接受参数:看官方文档
componentDidUpdate(prevProps, prevState){ //以下为伪代码 judgeProps() //获取新的props judgeState() //获取新的state handelDom() //操作DOM元素 ajax() //发起ajax请求 }
componentWillUnmount()
作用:
- 组件将要被移出页面被销毁之前执行代码
- 用法举例,在组件挂载后(监听事件,发起请求,增加定时器),那么就在此钩子取消(监听,定时器,请求)
关于forceUpdate()个人基本不用(官方也不推荐用),此处不赘述,看文档
钩子执行顺序
受控组件 && 受控组件
受控组件
React 的自身状态 state 成为“唯一数据源”。渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”。
class Child extends React.Component { constructor(props) { super(props); this.state = {value: ''} } handleChange = (event)=>{ this.setState({value: event.target.value}); } handleSubmit = (event)=> { console.log(this.state.value); event.preventDefault(); } render() { return ( <form onSubmit={this.handleSubmit}> <label> 名字: <input type="text" value={this.state.value} onChange={this.handleChange} /> </label> <input type="submit" value="提交" /> </form> ); } }
非受控组件
非受控组件表单数据将交由 DOM 节点来处理,react程序中通过ref得到DOM节点的引用
class Child extends React.Component { constructor(props) { super(props); this.input = React.createRef() //在React 16.3版本后,使用此方法来创建ref。将其赋值给一个变量,通过ref挂载在dom节点或组件上,该ref的current属性将能拿到dom节点或组件的实例 } handleSubmit = (event)=> { console.log(this.input.current.value); event.preventDefault(); } render() { return ( <form onSubmit={this.handleSubmit}> <label> Name: <input type="text" ref={this.input} /> //<input type="text" ref={ref=>this.input=ref} /> 通过回调函数的形式,也能拿到Dom的引用 //ref="input" 用字符串绑定,通过this.refs.[字符串]也能拿到Dom引用,官方已不推荐此方式,未来可能会被弃用 </label> <input type="submit" value="Submit" /> </form> ); } }
CSS方案
关于React的CSS方案,主流的有行内样式,引入样式,CSS Modules,Styled Components。不推荐使用引入CSS文件的方案(样式重置及自定义全局样式除外),因为样式是全局的,可能会存在样式冲突问题,如有两个CSS文件,其中有样式名重复的情况下,其一就会被覆盖掉。推荐使用Styled Components,与sass和less大部分语法类似,如嵌套和继承。
字符串模板介绍
//先通过npm进行安装 npm i styled-components
一些简单用法介绍
需要先引入 import styled from 'styled-components' //创建了一个TestDiv样式组件,它将渲染一个带样式的div标签 //组件名首字母仍需要大写,标签名后面的是字符串模板 const TestDiv = styled.div` background:red; //嵌套样式,该样式组件中的p标签都会应用此样式 p{ font-size:3em; } color: ${props => props.color}; //可以通过props获得父组件传入参数,进行赋值等操作 background: ${props => props.primary ? "palevioletred" : "white"}; font-size: ${props => props.fontSize ? `${props.fontSize}px` : "10px"}; ` //继承了TestDiv的样式,并在此基础上额外增加样式 const ChildComponent = styled(TestDiv)` color: red; `
更多用法查看styled-components文档
在React项目中引入图片,import与require遵循的模块化规范不一样
使用import
import Img from './images/1.png' <img src={Img} alt='' />
使用require
<img src={require("./images/1.png")} alt="">
内联样式
style={{background: `url(${require("./images/1.png")})`}} import bgImg from './images/1.png' style={{background: {bgImg}}}
在React项目中发送网络请求
举例一些常用方案
- jQuery $.ajax
- Fetch API
- Axios
- Request
- SuperAgent
推荐使用Axios,API完善,功能强大
React项目的路由方案
使用react-router-dom 官方文档
React主流UI库推荐
使用第三方UI库可以节约开发时间成本,保证规范和唯一性。不管是自己编写的组件还是使用第三方UI库,都无法避免缺陷的存在,尽量选择经过市场考验的库(可以参考项目在Github的Star数)。