react项目搭建create-router-dom,redux详细解说

react项目搭建create-router-dom,redux详细解说

1.搭建react脚手架

首先选择脚手架,dav-cli,create-react-app,Ant-Design-Pro-cli。脚手架即为代码层次。这里我们选用create-react-app脚手架

打开我们的cmd,window+R输入cmd进入终端,然后安装我们的脚手架

javascript 复制代码
npm install -g create-react-app

脚手架安装完成后,我们开始创建react新项目,稍微等一下,创建比较慢

javascript 复制代码
create-react-app  名字

进入到当前项目

javascript 复制代码
cd dome-react

启动项目,下面红框包裹的为显示页面的链接

javascript 复制代码
npm start

到此一个基本的脚手架就已经创建好了

目录:

  1. node_modules存放项目依赖的第三方库和模块。这个目录是由 npm 自动生成的,不需要手动管理。
  2. public存放我们的静态资源
  3. src存放项目源代码的目录。这是你主要的开发目录。
  4. index入口文件,react的页面会渲染到index.html,root元素里面
  5. package.json项目的配置文件,包含项目的依赖、脚本、项目元数据等。你可以在这里定义项目的各种设置和依赖。
  6. APP.js 这是一个示例组件,作为应用的主要组件。通常,这个文件会包含应用的主要结构和逻辑。

2.React-route-dom路由使用

javascript 复制代码
npm i react-router-dom
路由的基本使用
  1. 明确好界面中那块是导航区那一块是展示区
  2. 导航区使用Link不再使用a标签
javascript 复制代码
<Link to="/xxxxx">Demo</Link>
  1. 展示区写Route标签进行路径的匹配
javascript 复制代码
<Route path='/xxxx' component={Demo}/>
path为路径 component为展示的页面路径

<App>的最外侧包裹了一个<BrowserRouter><HashRouter>

javascript 复制代码
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {BrowserRouter} from "react-router-dom"

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    <React.StrictMode>
        <BrowserRouter>
            <App/>
        </BrowserRouter>
    </React.StrictMode>
);

reportWebVitals();

HashRouter和BrowserRouter一样,只不过HashRoute在url里是有hash值的

javascript 复制代码
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {HashRouter} from "react-router-dom"

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    <React.StrictMode>
        <HashRouter>
            <App/>
        </HashRouter>
    </React.StrictMode>
);

reportWebVitals();
路由组件与一般组件

1.写法不同

一般组件:< Dome />

路由组件:< Route path='/xxxx' component={Demo} />

2.存放位置不同

一般组件:components

路由组件:pages,views

3.接收参数不同

一般组件:组件传什么过来,就接收什么过来

路由组件:接收固定的三个属性值

javascript 复制代码
history:
		go: ƒ go(n)
		goBack: ƒ goBack()
		goForward: ƒ goForward()
		push: ƒ push(path, state)
		replace: ƒ replace(path, state)
location:
		pathname: "/about"
		search: ""
		state: undefined
match:
		params: {}
		path: "/about"
		url: "/about"
Switch

在React中相同路径可以加载多个组件的,假如你不想,你可以用Switch包裹一下Route,这样即使相同路径下,也只是加载最上面的组件

javascript 复制代码
<Switch>
     <Route path="/home/news" component={News}/>
     <Route path="/home/massage" component={Message}/>
     <Route path="/home/massage" component={Detail}/>
     <Redirect to="/home/news"/>
</Switch>
Redirect

在react中我们想在一开始默认加载哪个组件或者路径不适配的时候我们可以用Redirect标签,可以给我们重定向加载任何一个组件,但Redirect一般出现在Route的最下面

javascript 复制代码
<Switch>
     <Route path="/home/news" component={News}/>
     <Route path="/home/massage" component={Message}/>
     <Redirect to="/home/news"/>
</Switch>

路由的嵌套

假设现在我们需要给予选中路由一个高亮我们可以使用NavLink标签,我们可以把他单独封装成一个公用的路由组件

javascript 复制代码
import React, {Component, Fragment} from "react"
import {NavLink} from 'react-router-dom'
import "./MyNavLink.css"

export default class MyNavLink extends Component {
    render() {
        return (
            <Fragment>
                <div className="btn-div">
                    <NavLink activeClassName="active" className="btn-a" {...this.props}/>
                </div>
            </Fragment>
        )
    }
}

注意高亮默认在React-Router中是action属性

如果我们想在react项目中,写入多级路由的话,我们需要在Link,Route以及Redirect中加入父级路由

父级路由组件
javascript 复制代码
import {Fragment} from "react"
import {Route, Switch, Redirect} from "react-router-dom"
import Title from "./component/Title";
import About from "./pages/About";
import Home from "./pages/Home";
import MyNavLink from "./MyNavLink";

import './App.css';

function App() {
    return (
        <Fragment>
            <Title/>
            <MyNavLink to="/about">About</MyNavLink>
            <MyNavLink to='/home'>Home</MyNavLink>
            <Switch>
                <Route path="/about" component={About}/>
                <Route path="/home" component={Home}/>
                <Redirect to="/about"/>
            </Switch>
        </Fragment>
    );
}

export default App;
子组件路由
javascript 复制代码
import React, {Component, Fragment} from "react"
import {Switch, Route, Redirect} from 'react-router-dom'
import MyNavLink from "../../MyNavLink";
import Message from "./Message";
import News from "./News";

export default class Home extends Component {
    render() {
        return (
            <Fragment>
                <h1>我是Home</h1><br/>
                <MyNavLink to="/home/news">News</MyNavLink>
                <MyNavLink to="/home/massage">Massage</MyNavLink>
                <Switch>
                    <Route path="/home/news" component={News}/>
                    <Route path="/home/massage" component={Message}/>
                    <Redirect to="/home/news"/>
                </Switch>
            </Fragment>
        )
    }
}
路由的模糊匹配
javascript 复制代码
<Fragment>
     <Title/>
     <MyNavLink to="/about">About</MyNavLink>
     <MyNavLink to='/home/a/b'>Home</MyNavLink>
     <Switch>
          <Route path="/about" component={About}/>
          <Route path="/home" component={Home}/>
          <Redirect to="/about"/>
     </Switch>
</Fragment>

像这样的路由我们多传,虽然路径不匹配但在模糊匹配下可以让react渲染Home组件,但是不可以头路径不正确

例如:

javascript 复制代码
<Fragment>
     <Title/>
     <MyNavLink to="/about">About</MyNavLink>
     <MyNavLink to='/a/home'>Home</MyNavLink>
     <Switch>
          <Route path="/about" component={About}/>
          <Route path="/home" component={Home}/>
          <Redirect to="/about"/>
     </Switch>
</Fragment>

这样前面a/home是错误的,匹配不到的

路由的严格匹配

假如现在我们不希望进行模糊匹配,严格按照路径的话我们就可以开启严格匹配模式

javascript 复制代码
<Fragment>
     <Title/>
     <MyNavLink to="/about">About</MyNavLink>
     <MyNavLink to='/a/home'>Home</MyNavLink>
     <Switch>
          <Route exact={true} path="/about" component={About}/>
          <Route exact path="/home" component={Home}/>
          <Redirect to="/about"/>
     </Switch>
</Fragment>
路由的参数
传递Params参数
javascript 复制代码
this.state.massageArr.map(item => {
                            return <li key={item.id}>
                                <Link to={`/home/massage/detail/${item.id}/${item.title}`}>
                                    {item.title}
                                </Link>
                            </li>
                        })

我们通过数组的映射以及模版字符串的方式传递了两个Params参数

接收Params参数
javascript 复制代码
<Route path="/home/massage/detail/:id/:title" component={Detail}/>
调用Params参数
javascript 复制代码
export default class Detail extends Component {
    render() {
        const {id, title} = this.props.match.params
        const findResult = data.find((_item) => {
            return _item.id === id
        })
        return (
            <Fragment>
                <ul>
                    <li>id:{id}</li>
                    <li>title:{title}</li>
                    <li>content:{findResult.content}</li>
                </ul>
            </Fragment>
        )
    }
}

我们可以通过props里面的match里面封装的params里面将传递的值解构出来使用

传递Search参数
javascript 复制代码
this.state.massageArr.map(item => {
                            return <li key={item.id}>
                                <Link to={`/home/massage/detail?id=${item.id}&title=${item.title}`}>
                                    {item.title}
                                </Link>
                            </li>
                        })

同样我们也传递了两个Search参数,但注意Search参数不需要接收

querystring库

urlencoded编码

javascript 复制代码
key=value&key=value

像这样的编码格式被称为urlencoded编码格式

利用 querystring转化格式
javascript 复制代码
import qs from "querystring"

qs.stringify()  将对象转化为urlencoded
qs.parse()      将urlencoded转化为js中的对象
调用Search参数
javascript 复制代码
import React, {Component, Fragment} from "react"
import qs from "querystring"

const data = [
    {id: "01", content: "你好,宁夏"},
    {id: "02", content: "你好,吉林"},
    {id: "03", content: "你好,陕西"},
]

export default class Detail extends Component {
    render() {
        const {search} = this.props.location
        const {id, title} = qs.parse(search.slice(1))
        const findResult = data.find((_item) => {
            return _item.id === id
        })
        return (
            <Fragment>
                <ul>
                    <li>id:{id}</li>
                    <li>title:{title}</li>
                    <li>content:{findResult.content}</li>
                </ul>
            </Fragment>
        )
    }
}

如果没有这个第三方库我们可以安装

css 复制代码
npm i querystring
state参数
javascript 复制代码
this.state.massageArr.map(item => {
       return <li key={item.id}>
<Link to={{pathname: "/home/massage/detail", state: {id: item.id, title: item.title}}}>
                                    {item.title}
                                </Link>
                            </li>
                        })
调用state
javascript 复制代码
import React, {Component, Fragment} from "react"

const data = [
    {id: "01", content: "你好,宁夏"},
    {id: "02", content: "你好,吉林"},
    {id: "03", content: "你好,陕西"},
]

export default class Detail extends Component {
    render() {
        const {id, title} = this.props.location.state
        const findResult = data.find((_item) => {
            return _item.id === id
        })
        return (
            <Fragment>
                <ul>
                    <li>id:{id}</li>
                    <li>title:{title}</li>
                    <li>content:{findResult.content}</li>
                </ul>
            </Fragment>
        )
    }
}
编程式路由导航

编程式路由导航push是压栈式的,浏览器可以回退,有保存记录。而replace是覆盖浏览器的栈顶不能回退

replace编程式路由导航
javascript 复制代码
const replaceShow = (id, title) => {     this.props.history.replace(`/home/massage/detail/${id}/${title}`)
    }

注意只有在路由组件里面才可以使用这两个方法

push编程式路由导航
javascript 复制代码
const pushShow = (id, title) => {   this.props.history.push(`/home/massage/detail/${id}/${title}`)
    }
withRouter

如果我们想在非路由组件里调用路由组件的方法我们就可以用withRouter

javascript 复制代码
import React, {Component, Fragment} from "react"
import {withRouter} from "react-router-dom"

class Title extends Component {

    back = () => {
        this.props.history.goBack()
    }

    forWord = () => {
        this.props.history.goForward()
    }

    render() {
        return (
            <Fragment>
                <h2 style={{marginLeft: "50px"}}>React Router Demo</h2>
                <button onClick={this.forWord}>前进</button>
                <button onClick={this.back}>后退</button>
                <hr/>
            </Fragment>
        )
    }
}

export default withRouter(Title)

注意:goBack为历史记录的后退,而goForward为前进,两个函数后可以在路由组件找到

React-Router-Dom6

安装

javascript 复制代码
npm i react-router-dom@6

内置组件以及写法有些改变,函数式组件被官方明确推荐,又新增10个hooks

删除Switch,新增Routes
javascript 复制代码
import {NavLink, Routes, Route} from "react-router-dom"

<Routes>
    <Route path="/about" element={<About/>}/>
    <Route path="/home" element={<Home/>}/>
</Routes>
重定向
javascript 复制代码
import {NavLink, Routes, Route} from "react-router-dom"

<Routes>
    <Route path="/about" element={<About/>}/>
    <Route path="/home" element={<Home/>}/>
    <Route path="/" element={<Navigate to="/about"/>}/>
</Routes>
高亮
javascript 复制代码
<NavLink to="/about" className={({isActive}) => isActive ? "list-group-item active":"list-group-item"}>About</NavLink>

在react-router-dom6中可以用函数的方式返回出样式

路由表
javascript 复制代码
import {NavLink, useRoutes} from "react-router-dom"
import route from "@/route/index"
const element = useRoutes(route)

我们可以通过路由表的方式生成路由

route/index

javascript 复制代码
import About from "../pages/About";
import Home from "../pages/Home";
import {Navigate} from "react-router-dom";

export const route = [
    {
        path: "/about",
        element: <About/>
    },
    {
        path: "/home",
        element: <Home/>
    },
    {
        path: "/",
        element: <Navigate to="/about"/>
    }
]

多级路由也可以写在路由表里,但是需要Outlet配合,类似Vue的roter-view

多级路由的路由表

javascript 复制代码
import About from "../pages/About";
import Home from "../pages/Home";
import Message from "../pages/Message";
import News from "../pages/News";
import {Navigate} from "react-router-dom";

export const route = [
    {
        path: "/about",
        element: <About/>
    },
    {
        path: "/home",
        element: <Home/>,
        children: [
            {
                path: "news",
                element: <News/>
            },
            {
                path: "message",
                element: <Message/>
            }
        ]
    },
    {
        path: "/",
        element: <Navigate to="/about"/>
    }
]

子路由可以用children来写,类型也是数组,注意路径不要加 "/"

Outlet是子路由的出口

javascript 复制代码
import React, {Fragment, useState} from "react"
import {Navigate,Outlet,NavLink} from "react-router-dom"

export default function Home() {
    const [sum, setSum] = useState(1)

    return (
        <Fragment>
            <h3>Home</h3>
            {sum === 2 ? <Navigate to="/about"/> : <h4>当前sum为{sum}</h4>}
            <button onClick={() => setSum(sum + 1)}>点我加一</button>
            <NavLink to="message">message</NavLink>
            <NavLink to='news'>news</NavLink>
            <Outlet/>
        </Fragment>
    )
}

Params参数和之前的一样传,接收参数有所改变

javascript 复制代码
import React, {Fragment} from "react"
import {useParams} from "react-router-dom"

export default function Detail() {
    const {id, title, content} = useParams()
    return (
        <Fragment>
            <ol>
                <li>{id}</li>
                <li>{title}</li>
                <li>{content}</li>
            </ol>
        </Fragment>
    )
}
Search参数接收
javascript 复制代码
import React, {Fragment} from "react"
import {useSearchParams} from "react-router-dom"

export default function Detail() {
    const [search, setSearch] = useSearchParams()
    const id = search.get("id")
    const title = search.get("title")
    const content = search.get("content")
    return (
        <Fragment>
            <ol>
                <li>{id}</li>
                <li>{title}</li>
                <li>{content}</li>
            </ol>
            <button onClick={() => {
                setSearch('id=008&title=哈哈&content=嘻嘻')
            }}>点我更新Search
            </button>
        </Fragment>
    )
}
state参数
javascript 复制代码
import React, {Fragment, useState} from "react"
import {Link, Outlet} from "react-router-dom"

export default function Message() {

    const [message] = useState([
        {id: "001", title: "消息1", content: "锄禾日当午"},
        {id: "002", title: "消息2", content: "汗滴禾下土"},
        {id: "003", title: "消息3", content: "谁知盘中餐"},
        {id: "004", title: "消息4", content: "粒粒皆辛苦"},
    ])

    return (
        <Fragment>
            <ul>
                {
                    message.map((_item) => {
                        return <li key={_item.id}><Link
                            to="detail"
                            state={{
                                id: _item.id,
                                title: _item.title,
                                content: _item.content
                            }}
                        >{_item.title}
                        </Link></li>
                    })
                }
            </ul>
            <hr/>
            <Outlet/>
        </Fragment>
    )
}
useLocation
javascript 复制代码
import React, {Fragment} from "react"
import {useLocation} from "react-router-dom"

export default function Detail() {
    const {state: {id, title, content}} = useLocation()
    return (
        <Fragment>
            <ol>
                <li>{id}</li>
                <li>{title}</li>
                <li>{content}</li>
            </ol>
        </Fragment>
    )
}

我们需要连续解构赋值,在useLocation中将状态state解构,再将想要的状态从stae中结构

编程式路由导航
useNavigate
javascript 复制代码
import React, {Fragment, useState} from "react"
import {Link, Outlet, useNavigate} from "react-router-dom"

export default function Message() {
    const navigate = useNavigate()

    function showDetail(item) {
        navigate("detail", {
            replace: false,
            state: {
                id: item.id,
                title: item.title,
                content: item.content
            }
        })
    }

    const [message] = useState([
        {id: "001", title: "消息1", content: "锄禾日当午"},
        {id: "002", title: "消息2", content: "汗滴禾下土"},
        {id: "003", title: "消息3", content: "谁知盘中餐"},
        {id: "004", title: "消息4", content: "粒粒皆辛苦"},
    ])

    return (
        <Fragment>
            <ul>
                {
                    message.map((_item) => {
                        return <li key={_item.id}><Link
                            to="detail"
                            state={{
                                id: _item.id,
                                title: _item.title,
                                content: _item.content
                            }}
                        >{_item.title}
                        </Link>
                            <button onClick={() => {
                                showDetail(_item)
                            }}>查看详情
                            </button>
                        </li>
                    })
                }

            </ul>
            <hr/>
            <Outlet/>
        </Fragment>
    )
}
前进和后退
javascript 复制代码
import React, {Fragment} from "react";
import {useNavigate} from "react-router-dom"

export default function Title() {

    const navigate = useNavigate()

    function back() {
        navigate(-1)
    }

    function forWord() {
        navigate(1)
    }

    return (
        <Fragment>
            <h2 style={{marginLeft: "50px"}}>React Router Demo</h2>
            <button onClick={forWord}>前进-》</button>
            <button onClick={back}>《-后退</button>
            <hr/>
        </Fragment>
    )
}

Redux的基本使用

介紹:
  1. redux是一个专门用于做状态管理的JS库(不是react插件库)
  2. 它可以用在react, angular, vue等项目中, 但基本与react配合使用
  3. 作用: 集中式管理react应用中多个组件共享的状态
什么情况下需要使用redux
  1. 某个组件的状态,需要让其他组件可以随时拿到(共享)
  2. 一个组件需要改变另一个组件的状态(通信)。
  3. 总体原则:能不用就不用, 如果不用比较吃力才考虑使用

二,redux的三个核心概念

1,action
  1. 动作的对象
  2. 包含2个属性 type:标识属性, 值为字符串, 唯一, 必要属性 data:数据属性, 值类型任意, 可选属性
  3. 例子:{ type: 'ADD_STUDENT',data:{name: 'tom',age:18} }
2,reducer
  1. 用于初始化状态、加工状态。
  2. 加工时,根据旧的state和action, 产生新的state的纯函数。
3,store
  1. 将state、action、reducer联系在一起的对象

  2. 如何得到此对象?

javascript 复制代码
import {createStore} from 'redux'
import reducer from './reducers'
const store = createStore(reducer)
  1. 此对象的功能? getState(): 得到state dispatch(action): 分发action, 触发reducer调用, 产生新的state subscribe(listener): 注册监听, 当产生了新的state时, 自动调用

redux的核心API

1.createstore

作用:创建包含指定reducer的store对象

2,store对象
  1. 作用: redux库最核心的管理对象

  2. 它内部维护着:

    state reducer

  3. 核心方法:

  4. store.getState() store.dispatch({type:'INCREMENT', number}) store.subscribe(render)

3,applyMiddleware()

作用:应用上基于redux的中间件(插件库)

4,combineReducers()

作用:合并多个reducer函数

使用redux编写应用

安装:

javascript 复制代码
npm install react-redux
npm install @reduxjs/toolkit
2,新建文件

src路径下,新建store文件

3,定义仓库:
新建src / store / index.js
javascript 复制代码
// 定义仓库
// 引入configureStore 定义仓库
import { configureStore } from "@reduxjs/toolkit";
// 导入counterSlice
import counter from "./counterSlice";
// 导出
export const store = configureStore({
  // 数据处理
  reducer: {
    counter
  }
});
3,定义仓库:
新建src / store / index.js
javascript 复制代码
/*
 * @Author: hukai huzhengen@gmail.com
 * @Date: 2024-11-04 15:12:39
 * @LastEditors: hukai huzhengen@gmail.com
 * @LastEditTime: 2024-11-04 15:12:51
 * @FilePath: \dome-react\src\store\index.js
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
// 定义仓库
// 引入configureStore 定义仓库
import { configureStore } from "@reduxjs/toolkit";
// 导入counterSlice
import counter from "./counterSlice";
// 导出
export const store = configureStore({
    // 数据处理
    reducer: {
        counter
    }
});
4,创建计数器数据,及修改数据的方法:
新建src / store / counterSlice.js
javascript 复制代码
/*
 * @Author: hukai huzhengen@gmail.com
 * @Date: 2024-11-04 15:13:24
 * @LastEditors: hukai huzhengen@gmail.com
 * @LastEditTime: 2024-11-04 15:13:32
 * @FilePath: \dome-react\src\store\counterSlice.js
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
// 创建计数器切片slice
// 导入创建切片的函数
import { createSlice } from "@reduxjs/toolkit";
// 定义初始化状态
const initialState = { value: 0 };
// 创建切片
const counterSlice = createSlice({
    // 切片名称
    name: "counter",
    // 初始化状态
    initialState,
    // 定义处理器
    reducers: {
        // 处理加法
        increment: state => {
            state.value += 1;
        },
        // 处理减法
        decrement: state => {
            state.value -= 1;
        },
        // 处理加法
        addValue: (state, action) => {
            state.value += action.payload;
        }
    }
});

// 导出动作
export const { increment, decrement, addValue } = counterSlice.actions;
// 导出处理器
export default counterSlice.reducer;
// 导出异步操作动作
export const syncAddvalue = value => dispatch => {
    setTimeout(() => {
        dispatch(addValue(value));
    }, 2000);
};
5.在组件中使用redux
src / APP.js 文件:
javascript 复制代码
import {
  increment,
  decrement,
  addValue,
  syncAddvalue
} from "./store/counterSlice";
import { useSelector, useDispatch } from "react-redux";
const APP = () => {
  // 获取仓库数据
  const count = useSelector(state => state.counter.value);
  // 获取修改仓库数据的工具
  const dispatch = useDispatch();
  return (
    <div>
      <p>
        仓库数据:{count}
      </p>
      <button onClick={() => dispatch(increment())}>+1</button>
      <button onClick={() => dispatch(decrement())}>-1</button>
      <button onClick={() => dispatch(addValue(5))}>+5</button>
      <button onClick={() => dispatch(syncAddvalue(10))}>两秒后+10</button>
    </div>
  );
};

export default APP;

效果图

相关推荐
哑巴语天雨7 小时前
React+Vite项目框架
前端·react.js·前端框架
初遇你时动了情7 小时前
react 项目打包二级目 使用BrowserRouter 解决页面刷新404 找不到路由
前端·javascript·react.js
码农老起8 小时前
掌握 React:组件化开发与性能优化的实战指南
react.js·前端框架
前端没钱8 小时前
从 Vue 迈向 React:平滑过渡与关键注意点全解析
前端·vue.js·react.js
高山我梦口香糖11 小时前
[react] <NavLink>自带激活属性
前端·javascript·react.js
撸码到无法自拔11 小时前
React:组件、状态与事件处理的完整指南
前端·javascript·react.js·前端框架·ecmascript
高山我梦口香糖11 小时前
[react]不能将类型“string | undefined”分配给类型“To”。 不能将类型“undefined”分配给类型“To”
前端·javascript·react.js
乐闻x14 小时前
VSCode 插件开发实战(四):使用 React 实现自定义页面
ide·vscode·react.js
irisMoon0614 小时前
react项目框架了解
前端·javascript·react.js
web150850966411 天前
【React&前端】大屏适配解决方案&从框架结构到实现(超详细)(附代码)
前端·react.js·前端框架