Skip to content

Latest commit

 

History

History
336 lines (253 loc) · 8.58 KB

v1.0.md

File metadata and controls

336 lines (253 loc) · 8.58 KB

v1.0

[![](https://img.shields.io/badge/feature/slices-1.0-blue)](https://featureslices.dev/v1.0)

Usage

In that examples TypeScript used as common language, but you free to choose.

Applications

Examples

Introduction

FeatureSlices define your frontend application structure as easy and convenient.

Convention

  • All files and directories has name in param-case. Because it lowers cognitive load to select case for concrete file. One case for all files. File structure should define what type file have. Example: component-name.tsx

Structure

Source code of the application contained in src:

  • api
  • features
  • lib
  • pages
  • ui

Api

This is reusable functions or classes to make requests to the server. It is can be REST-like, GraphQL, WebSocket, etc.

Code in the api can be partially generated (swagger or OpenAPI).

What is API:

src/
  api/
    api-name.ts
    request.ts

For example api-name is a group of requests, having a common purpose. More that one namespace can be created.

Not recommended to add src/api/index.ts file, because different api should be separated one from one. If you import api/user, you see just requests related to user. But if you import api, you can be drowned in tons of requests for application.

Example:

src/
  api/
    post.ts
    feed.ts
    session.ts
    request.ts

In this example: post, feed, session is a namespaced requests, but request is a base function, to make requests.

Example post.ts:

import { request } from './request'

interface PostNew {
  title: string;
  content: string;
}

export const postsList = () => request({ method: "GET", path: "/posts" })

export const postCreate = (post: PostNew) => request({ method: "POST", path: "/posts", body: post })

export const postGet = (postId: string) => request({ method: "GET", path: `/posts/${postId}` })

Structure and shape of each method on your own. But it should be consistent with all other requests.

Recommended to setup imports in your project, to allow import from root (src/) or use some aliases.

From api source code you can import only src/lib and package dependencies, nothing more.

Good:

import { postsList } from 'api/post'
// or
import * as posts from '@api/post'

Bad:

import { posts } from '../../../api'

Features

It is list of your reusable code and shared state, groupped by one idea or entity.

Structure:

src/
  features/
    feature-name/
      components/
      models/
      templates/
      __tests__/
      index.ts
      readme.md
      stories.tsx
  • One feature can depends on another feature. But not recommended to do it too often.
  • Do not make cyclic dependencies, when feature A depends on feature B, feature B on C, and C depends on A. Prefer tree over graph.
  • Export all entities from your feature through index.ts. It is helps to support your feature easier, because through that public interface, you can known what is used, and what can be refactored without stress.
  • Write tests and documentation for feature, to ease understanding how to use it.
  • You can have as many features as you need, but no more.

Feature structure:

  • components/ here should be atoms, molecules and organisms of your features. All should be related to single idea or entity.
  • models/ here place business-logic, that can be reused (instantiated) or shared between other features and pages. Any shared state also here.
  • templates/ it is a place for atomic design templates for pages. Templates also should be relate to one idea or entity.
  • __tests__/ or tests/ — any name of the directory to write integration or smoke tests.
  • index.ts — public interface of your feature, all entities and units should be imported through this file. Import from inside is restricted.
    • Bad: import {Some} from 'features/example/components/some
    • Good: import {Some} from 'features/example'
  • readme.md — documentation of your feature
  • stories.tsx — if you use storybook

Lib

Common utils or helper code should be groupped by meaning and placed at its own library in this directory.

Example:

You have two functions: parse a string to a username, and parse an array of strings to list of usernames. Instead of placing a first function to a lib/string, and a second to a lib/array, group it by meaning: username. The best place: lib/username

Structure

src/
  lib/
    lib-name/
      index.ts
      readme.md
      tests.ts
  • Spend some time to think about name for library and internal structure.
  • Write documentation and test for your library.
  • Treat to library as it is writen to publish to npm. It should have good coverage, easy to understand documentation.

Pages

What is page? Page is a component rendered on specific url. Or another router's unit, that separates one component of another.

Structure:

src/
  pages/
    page-name/
      index.tsx
      model.ts
    another/
      nested/
        index.tsx
        model.ts
      index.tsx
      model.ts
    paths.ts
    routes.ts
    index.ts
  • Each page is a pair: component and model files.
  • In component file you should place page component, also you can place here another unique components that makes page rich.
  • Page component's name should be built from page's route with suffix Page. Example: /register/confirmRegisterConfirmPage
  • In model file you should place logic for that page.
  • Extra files:
    • paths.ts - contains map from route name to route path
    • routes.ts - contains common router configuration
    • index.ts - contains render for routes configuration
  • You can merge paths, routes and index in a single file, if you don't need it separated.

Example component file:

// src/pages/register/confirm/index.ts
import * as React from 'react'
import { Just } from '@app/ui'

import * as model from './model'

export const RegisterConfirmPage: React.FC = () => {
  model.useLogic();

  return (
    <RegisterConfirmContent>
      Hello from registration confirmation page
    </RegisterConfirmContent>
  )
}

const RegisterConfirmContent: React.FC = ({ children }) => (
  <Just example>
    {children}
  </Just>
)

Example model file:

// src/pages/register/confirm/model.ts
import * as React from 'react'

export const useLogic = () => {
  React.useEffect(() => {
    console.log("Handle on mount here")
  }, [])
}

Example paths file:

  • Functions is used here, to consistent create path with parameters and without.
  • You should use this functions in all application to redirect or create links. It is helps when you want to change path.
// src/pages/paths.ts

export const paths = {
  home: () => `/`,
  login: () => `/login`,
  register: () => `/register`,
  registerConfig: (code: string) => `/register/confirm/${code}`,
}

Example routes file:

// src/pages/routes.ts
import { paths } from './paths'

import { HomePage } from './home'
import { LoginPage } from './login'
import { RegisterPage } from './register'
import { RegisterConfirmPage } from './register/confirm'
import { Error404Page } from './error404'

// react-router-config example
export const routes = [
  {
    path: paths.home(),
    exact: true,
    component: HomePage,
  },
  {
    path: paths.login(),
    exact: true,
    component: LoginPage,
  },
  {
    path: paths.register(),
    exact: true,
    component: RegisterPage,
  },
  {
    path: paths.registerConfig(':code'), // pass path parameter
    exact: true,
    component: RegisterConfirmPage,
  },
  {
    path: '*',
    component: Error404Page,
  },
]

Example pages index file:

// src/pages/index.ts
import { renderRoutes } from 'react-router-config';
import { routes } from './routes'

export const Pages = () => renderRoutes(routes)

Now Pages component can be easily used in Application to bootstrap routing.

Example application file:

// src/application.tsx
import * as React from 'react'
import * as ReactDOM from 'react-dom'
import { BrowserRouter } from 'react-router-dom'
import { GlobalTemplate } from '@app/ui'
import { Pages } from './pages'

const Application = () => (
  <GlobalTemplate>
    <Pages />
  </GlobalTemplate>
)

ReactDOM.render(
  <BrowserRouter>
    <Application />
  </BrowserRouter>,
  document.querySelector("#root"),
)

UI

TO DO