Why did I decide to use the VAC pattern in my project?
Recently, I learned about the concept of separation of concerns and began studying various design patterns in-depth. Among them, I found the VAC (View-Assets-Component) pattern to be particularly suitable for separating and managing the interests of business logic and views in a systematic manner.
In practical work, UI developers and FE developers are sometimes separated, and their areas of work often overlap. Even though there were no dedicated UI developers in my project, I wanted to use a design pattern that could be applied in practical settings as well.
For these reasons, I introduced the VAC pattern to this project. In this article, I will share the advantages and disadvantages we encountered while applying the VAC pattern, as well as provide an introduction to the pattern itself.
Then let’s find out what the VAC pattern is first.
What is a VAC pattern?
The View Asset Component (VAC) pattern is a design method that aims to minimize the coupling between View Logic and JSX. It abstracts the JSX area into a ‘Props Object’ in View components and separates JSX into VAC. The VAC pattern follows the following rules:
- Only perform actions related to style control rendering, such as repetition, conditional exposure, etc.
- A stateless component that is controlled only through Props and does not manage its own state.
- Bind functions directly to events without further action.
While VAC components cannot have state, it is possible to have a component with state as a child. In this case, the VAC pattern does not intervene between the parent and child components but simply serves to deliver Props.
Compared to existing patterns, the VAC pattern provides a cleaner separation of concerns and makes it easier to reason about the codebase. By separating business logic and rendering concerns, it becomes easier to test and maintain the code. Additionally, the VAC pattern promotes the reuse of components, as they are designed to be stateless and modular.
Let’s compare the differences from the existing patterns.
Existing Pattern
- Handle
JSX
andstyle
within the component and bind the necessary callback functions related to the business logic. - Even if
Props
orState
is simply changed, if a UI developer has made changes to theJSX
area, there may be code conflicts when modifying theJSX
area.
Let’s take a look at the structure of the VAC
pattern to overcome these drawbacks.
VAC Pattern
- Split
JSX
into separate components to minimize the disadvantages of the existing pattern. - Manage the passed
Props
andState
as aProps Object
to make it easy to check the required attributes and callback functions forVAC
.
In other words, it can be considered a design pattern that provides a guide on how to separate View logic and JSX
.
I have applied this structure to my project.
Applying to a Project
View Component
const Sidebar = () => {
const [selectPage, setSelectPage] = useState<"myArt" | "createArt">("myArt");
const navigate = useNavigate();
const onNavigateGallery = () => {
setSelectPage("myArt");
navigate(ADMIN_ROUTE.MY_ART);
};
const onNavigateCreate = () => {
setSelectPage("createArt");
navigate(ADMIN_ROUTE.CREATE_ART);
};
const sideBarViewProps = {
onNavigateGallery,
onNavigateCreate,
selectPage
};
// Using JSX as VAC
return <SidebarView {...sideBarViewProps} />; // Props Object
};
export default Sidebar;
In the View component, functions, props
, state
, and callback
are implemented and delivered to VAC Component through a Props Object
.
If View logic is reused, it can be managed separately by HOC
or Hook
.
VAC Component
const SidebarView = ({
onNavigateGallery,
onNavigateCreate,
selectPage
}: TSideBarViewProps) => {
return (
<Container>
...
</Container>
);
};
In the VAC component, only JSX
is used.
Since the JSX
area always uses Props
as the received value, UI developers do not need to understand how Props
are configured. Only the delivered Props
can be used to develop JSX
.
There are some precautions to consider when applying VAC to a project.
Things to be cautious about
VAC
should not be involved in the functionality or state control of the View component as follows
// View Component
const SpinBox = () => {
const [value, setValue] = useState(0);
const props = {
value,
step: 1,
handleClick: (n) => setValue(n),
};
// Inappropriate VAC behavior of controlling value
return <SpinBoxView {...props} />;
};
The correct VAC
only binds the handler to the event and does not interfere with what to do.
// VAC
const SpinBoxView = ({ value, onIncrease, onDecrease }) => (
<div>
<button onClick={onDecrease}>-</button>
<span>{value}</span>
<button onClick={onIncrease}>+</button>
</div>
);
Scenario in Practical Work
- The FE developer modifies the logic in the
Props Object
of theView
component locally. - The UI developer applies
style
in theVAC
. - The FE developer modifies the functionality in the View component, and the UI developer modifies the
JSX
in theVAC
to avoid conflicts.
Naming of VAC props
The props should be named in a way that is intuitive for rendering, rather than being data-centric.
For example:
- Use
disabledDecrease
anddisabledIncrease
instead ofisMax
andisMin
. - Use
showEditButton: isLogin && isOwner
instead of passingisLogin
andisOwner
separately and checkingisLogin && isOwner
in the VAC.
This makes it easy to infer the role of the props in rendering.
Difference between VAC Pattern and Presentational Container Pattern
The VAC
pattern is a type of Presentational and Container component pattern, as it delegates the logic to the container component.
However, the difference between the two patterns lies in whether the components can have View logic (UI functionality, state management) or not.
Presentational Components
- Aim to separate business logic and View concerns.
- Manage business logic in the container components and control the Presentational components.
- Presentational components handle View logic (UI functionality, state management) and rendering.
VAC
- Aims to separate View logic (UI functionality, state management) and rendering (JSX) concerns.
- The View component serves as the container component for
VAC
and manages aProps Object
that abstracts the JSX for the VAC. - VAC manages to render with
JSX
andStyle
.
VAC
provides more specific criteria than Presentational components and helps to achieve consistent design from the perspective of the component that handles JSX
rendering.
Review after applying the VAC pattern
Pros
- Business logic and UI are separated, making it easy to understand the code.
- It is easy to make changes. If you want to change the CSS library, you only need to change the VAC component.
- VAC components can be designed and styled based solely on the props interface that is passed down. You don’t need to worry about how the props are passed down.
Cons
- There is often drilling of props, which can be inconvenient.
- It is not easy to apply the pattern when directly manipulating the DOM.
- Props can become unwieldy.
Due to the distinction between business logic and UI, props can become bulky.
- The VAC component criteria are ambiguous.
Review
Even simple components require a separation of logic and style, which can lead to confusion.
After learning about the VAC pattern and applying it to a project, I think that every design pattern has its own pros and cons. However, if you think in terms of the separation of concerns or collaboration with UI developers, the separation of business logic and UI is very suitable for the purpose.
I think the VAC pattern is a React design pattern worth studying and applying at least once.
References
React VAC Pattern — View 로직과 JSX의 의존성을 최소화 하자!